Dockerizing and Orchestrating Legacy Magento 2 Systems on Modern DigitalOcean Infrastructure
Understanding the Challenges of Dockerizing Legacy Magento 2
Migrating a legacy Magento 2 installation to a containerized environment, especially on modern cloud infrastructure like DigitalOcean, presents unique hurdles. These often stem from deeply ingrained dependencies, specific PHP version requirements, complex file system permissions, and the intricate web of background processes (cron jobs, message queues) that Magento relies upon. A common pitfall is assuming a direct lift-and-shift will work; it rarely does without significant refactoring. We’ll focus on a pragmatic approach, addressing these challenges head-on with specific configurations and strategies.
Crafting the Core Dockerfile for Magento 2
The foundation of our containerization strategy is a robust Dockerfile. For legacy systems, we often need to pin specific versions of PHP and its extensions, which might differ from current best practices. We’ll also need to manage Magento’s application structure and ensure proper ownership for web server access.
Here’s a sample Dockerfile that balances flexibility with the needs of a legacy Magento 2 instance. This example uses PHP 7.4, a common requirement for older Magento versions, and includes essential extensions. We’ll also set up a non-root user for security best practices.
Dockerfile:
# Use an official PHP runtime as a parent image
FROM php:7.4-fpm
# Set environment variables
ENV MAGENTO_ROOT=/var/www/html \
PHP_MEMORY_LIMIT=512M \
PHP_MAX_EXECUTION_TIME=1800 \
PHP_UPLOAD_MAX_FILESIZE=64M \
PHP_POST_MAX_SIZE=64M \
MAGENTO_UID=1000 \
MAGENTO_GID=1000
# Install system dependencies and PHP extensions
RUN apt-get update && apt-get install -y \
git \
unzip \
libzip-dev \
libpng-dev \
libjpeg-dev \
libfreetype6-dev \
libssl-dev \
libicu-dev \
libxslt1-dev \
libcurl4-openssl-dev \
libonig-dev \
libxml2-dev \
zlib1g-dev \
acl \
supervisor \
&& rm -rf /var/lib/apt/lists/* \
# Install PHP extensions
&& docker-php-ext-configure gd --with-freetype --with-jpeg \
&& docker-php-ext-install -j$(nproc) gd \
&& docker-php-ext-install -j$(nproc) zip \
&& docker-php-ext-install -j$(nproc) intl \
&& docker-php-ext-install -j$(nproc) opcache \
&& docker-php-ext-install -j$(nproc) bcmath \
&& docker-php-ext-install -j$(nproc) sockets \
&& docker-php-ext-install -j$(nproc) exif \
&& docker-php-ext-install -j$(nproc) pcntl \
&& docker-php-ext-install -j$(nproc) pdo_mysql \
&& pecl install redis \
&& docker-php-ext-enable redis \
&& pecl install xdebug \
&& docker-php-ext-enable xdebug \
# Configure PHP settings
&& sed -i 's/memory_limit = .*/memory_limit = ${PHP_MEMORY_LIMIT}/' /usr/local/etc/php/conf.d/docker-php-ext-memory-limit.ini \
&& sed -i 's/max_execution_time = .*/max_execution_time = ${PHP_MAX_EXECUTION_TIME}/' /usr/local/etc/php/php.ini \
&& sed -i 's/upload_max_filesize = .*/upload_max_filesize = ${PHP_UPLOAD_MAX_FILESIZE}/' /usr/local/etc/php/php.ini \
&& sed -i 's/post_max_size = .*/post_max_size = ${PHP_POST_MAX_SIZE}/' /usr/local/etc/php/php.ini \
&& sed -i 's/display_errors = .*/display_errors = Off/' /usr/local/etc/php/php.ini \
&& sed -i 's/log_errors = .*/log_errors = On/' /usr/local/etc/php/php.ini \
&& sed -i 's/error_log = .*/error_log = \/var\/log\/php-fpm.log/' /usr/local/etc/php-fpm.conf \
# Install Composer
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer \
# Create Magento user and directory
&& groupadd -g ${MAGENTO_GID} magento \
&& useradd -u ${MAGENTO_UID} -g magento -ms /bin/bash magento \
&& mkdir -p ${MAGENTO_ROOT} \
&& chown -R magento:magento ${MAGENTO_ROOT} \
# Clean up apt cache
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
# Copy Magento application code (will be mounted as a volume or copied later)
# COPY . ${MAGENTO_ROOT}
# Set working directory
WORKDIR ${MAGENTO_ROOT}
# Expose port for PHP-FPM
EXPOSE 9000
# Copy supervisor configuration
COPY docker/supervisor/php-fpm.conf /etc/supervisor/conf.d/php-fpm.conf
COPY docker/supervisor/cron.conf /etc/supervisor/conf.d/cron.conf
# Start supervisor
CMD ["/usr/bin/supervisord", "-c", "/etc/supervisor/supervisord.conf"]
Configuring Nginx for Magento 2 in Docker
Magento 2 requires a specific Nginx configuration to handle static files, rewrites, and proxying to the PHP-FPM service. We’ll create a separate container for Nginx and define its configuration.
Nginx Configuration (nginx.conf):
# nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;
events {
worker_connections 768;
multi_accept on;
}
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 2048;
include /etc/nginx/mime.types;
default_type application/octet-stream;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log warn;
gzip on;
gzip_disable "msie6";
# Magento 2 specific configuration
include /etc/nginx/conf.d/*.conf;
}
Magento VHost Configuration (magento.conf):
# magento.conf
server {
listen 80;
server_name your_domain.com; # Replace with your domain
# Magento root directory
root /var/www/html/pub; # Assuming Magento is installed in /var/www/html
index index.php index.html index.htm;
location / {
try_files $uri $uri/ /index.php?$args;
}
# Serve static files directly
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
expires 1y;
add_header Cache-Control "public";
access_log off;
log_not_found off;
}
# PHP-fpm configuration
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php-fpm:9000; # Assuming your PHP-FPM service is named 'php-fpm'
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
# Magento specific fastcgi params
fastcgi_param MAGE_RUN_CODE default; # Replace 'default' with your Magento store view code if applicable
fastcgi_param MAGE_RUN_TYPE website; # Or 'store' or 'store_view'
}
# Deny access to sensitive files
location ~* /(composer\.json|composer\.lock|\.git|\.env|\.htaccess|LICENSE|README\.md) {
deny all;
}
# Deny access to var/log, var/report, var/session, var/cache, etc.
location ~ ^/(var/|bin/|update/|app/etc/) {
deny all;
}
# Magento static content deployment cache
location /static/ {
autoindex off;
expires 1y;
add_header Cache-Control "public";
try_files $uri $uri/ /static.php?$args;
}
# Magento media files
location /media/ {
autoindex off;
expires 1y;
add_header Cache-Control "public";
try_files $uri $uri/ /media.php?$args;
}
# Magento admin URL protection (optional, but recommended)
# location /admin_xxxxxx/ {
# # Add IP restrictions or basic auth here
# }
}
Database and Cache Management
Magento 2 relies heavily on a robust database (MySQL/MariaDB) and caching mechanisms (Redis, Varnish). These should ideally be managed as separate services, not within the Magento application container.
Database Setup (docker-compose.yml snippet):
services:
db:
image: mysql:5.7 # Or mariadb:10.4, ensure compatibility with your Magento version
container_name: magento_db
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: magento_db
MYSQL_USER: magento_user
MYSQL_PASSWORD: magento_password
volumes:
- db_data:/var/lib/mysql
ports:
- "3306:3306" # Only expose if needed for direct access during development/debugging
redis:
image: redis:6.2
container_name: magento_redis
restart: always
ports:
- "6379:6379" # Only expose if needed for direct access during development/debugging
volumes:
db_data:
Magento Configuration (app/etc/env.php):
<?php
return [
'backend' => [
'frontName' => 'admin_xxxxxx' // Change this to your admin URL
],
'crypt' => [
'key' => 'your_crypt_key' // Ensure this is set and unique
],
'db' => [
'table_prefix' => '',
'connection' => [
'default' => [
'host' => 'db', // Service name from docker-compose.yml
'dbname' => 'magento_db',
'username' => 'magento_user',
'password' => 'magento_password',
'model' => 'mysql4',
'engine' => 'innodb',
'initStatements' => 'SET NAMES utf8mb4',
'options' => [
PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8mb4'
]
]
]
],
'cache' => [
'frontend' => [
'default' => [
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'backend_options' => [
'server' => 'redis', // Service name from docker-compose.yml
'database' => '0',
'port' => '6379'
]
],
'page_cache' => [
'backend' => 'Magento\\Framework\\Cache\\Backend\\Redis',
'backend_options' => [
'server' => 'redis',
'database' => '1',
'port' => '6379'
]
]
]
],
'resource' => [
'default_setup' => [
'connection' => 'default'
]
],
'directories' => [
'document_root' => '/var/www/html',
'cache_dir' => '/var/www/html/var/cache',
'session_dir' => '/var/www/html/var/session'
],
'session' => [
'save' => 'redis',
'redis' => [
'host' => 'redis',
'port' => '6379',
'password' => '',
'timeout' => '2.5',
'persistent_identifier' => '',
'database' => '2',
'compression_threshold' => '2048',
'compression_library' => 'gzip',
'log_level' => '3',
'max_concurrency' => '6',
'break_after_frontend' => '5',
'break_on_frontend_fail' => '3',
'fail_after' => '10',
'lifetimelimit' => '5',
'compress_data' => '1',
'compress_tags' => '1',
'compress_threshold' => '2048',
'compression_lib' => 'gzip'
]
]
];
Orchestration with Docker Compose
Docker Compose is essential for defining and managing multi-container Docker applications. It allows us to link our Magento, Nginx, PHP-FPM, database, and Redis services together.
Complete docker-compose.yml:
version: '3.8'
services:
php-fpm:
build:
context: .
dockerfile: Dockerfile
container_name: magento_php_fpm
restart: always
volumes:
- ./pub:/var/www/html/pub # Mount pub for static content
- ./app:/var/www/html/app
- ./vendor:/var/www/html/vendor
- ./composer.json:/var/www/html/composer.json
- ./composer.lock:/var/www/html/composer.lock
- ./bin:/var/www/html/bin
- ./generated:/var/www/html/generated
- ./var:/var/www/html/var
# Add other necessary Magento directories if not part of the build context
environment:
# Pass Magento specific environment variables if needed
- MAGENTO_UID=${MAGENTO_UID:-1000}
- MAGENTO_GID=${MAGENTO_GID:-1000}
- DB_HOST=db
- DB_NAME=magento_db
- DB_USER=magento_user
- DB_PASSWORD=magento_password
- REDIS_HOST=redis
- REDIS_PORT=6379
depends_on:
- db
- redis
networks:
- magento_network
nginx:
image: nginx:latest
container_name: magento_nginx
restart: always
ports:
- "80:80"
- "443:443" # If using SSL
volumes:
- ./pub:/var/www/html/pub:ro # Read-only for Nginx
- ./app:/var/www/html/app:ro
- ./vendor:/var/www/html/vendor:ro
- ./generated:/var/www/html/generated:ro
- ./var/view_preprocessed:/var/www/html/var/view_preprocessed:ro
- ./var/static-content:/var/www/html/var/static-content:ro
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./magento.conf:/etc/nginx/conf.d/magento.conf:ro
# SSL certificates if applicable
# - ./ssl/your_domain.crt:/etc/nginx/ssl/your_domain.crt:ro
# - ./ssl/your_domain.key:/etc/nginx/ssl/your_domain.key:ro
depends_on:
- php-fpm
networks:
- magento_network
db:
image: mysql:5.7 # Or mariadb:10.4
container_name: magento_db
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: magento_db
MYSQL_USER: magento_user
MYSQL_PASSWORD: magento_password
volumes:
- db_data:/var/lib/mysql
networks:
- magento_network
redis:
image: redis:6.2
container_name: magento_redis
restart: always
volumes:
- redis_data:/data
networks:
- magento_network
volumes:
db_data:
redis_data:
networks:
magento_network:
driver: bridge
Handling Magento CLI Commands and Deployments
Executing Magento CLI commands (e.g., `bin/magento setup:upgrade`, `bin/magento setup:static-content:deploy`) requires interacting with the PHP-FPM container. We can achieve this by using `docker-compose exec`.
Example: Deploying Static Content
# Ensure your .env file or environment variables are set for the correct Magento instance # Build the images if you haven't already docker-compose build # Start the services docker-compose up -d # Execute static content deployment within the php-fpm container # Ensure the MAGENTO_UID and MAGENTO_GID environment variables are set correctly in your .env or docker-compose.yml docker-compose exec -u magento php-fpm bin/magento setup:static-content:deploy -f en_US en_GB # Add your desired locales # Clear cache docker-compose exec -u magento php-fpm bin/magento cache:clean docker-compose exec -u magento php-fpm bin/magento cache:flush # Reindex (if necessary) # docker-compose exec -u magento php-fpm bin/magento indexer:reindex
For production deployments, consider using a CI/CD pipeline that builds the Magento application into the PHP-FPM image or a separate application image, then deploys that image. Static content and media can be pre-compiled or deployed during the build process.
DigitalOcean Specific Considerations
When deploying to DigitalOcean, leverage their managed database services (Managed Databases for MySQL/PostgreSQL) and managed Redis for better scalability and reliability. This offloads the operational burden of managing these critical components.
Using DigitalOcean Managed Databases:
- Provision a Managed Database cluster (e.g., MySQL 5.7 or 8.0).
- Obtain the connection string, username, password, and host from the DigitalOcean control panel.
- Update your
app/etc/env.phpfile to use these credentials, replacing the Docker Compose service names (`db`, `redis`) with the actual hostnames provided by DigitalOcean. - Ensure your firewall rules on DigitalOcean allow your Droplets to connect to the managed database endpoint.
Using DigitalOcean Managed Redis:
- Provision a Managed Redis cluster.
- Update your
app/etc/env.phpfile with the connection details (host, port, password) for the managed Redis service. - Ensure firewall rules permit access.
For orchestrating your containers on DigitalOcean, consider using DigitalOcean Kubernetes (DOKS) for more advanced scaling and management, or simply run your Docker Compose setup on a dedicated Droplet. For a single Droplet setup, ensure sufficient CPU, RAM, and disk I/O. A general-purpose or CPU-optimized Droplet is usually a good starting point.
Troubleshooting Common Issues
Permissions Errors: Magento is notoriously sensitive to file permissions. Ensure the `magento` user within the PHP-FPM container has the correct ownership and permissions for the Magento directories, especially `var/`, `generated/`, and `pub/static/`. Use `setfacl` for more granular control if needed.
# Example: Correcting permissions within the container
docker-compose exec -u root php-fpm bash -c "chown -R magento:magento /var/www/html/var /var/www/html/generated /var/www/html/pub/static /var/www/html/pub/media"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/var -type d -exec chmod 755 {} \;"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/var -type f -exec chmod 644 {} \;"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/generated -type d -exec chmod 755 {} \;"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/generated -type f -exec chmod 644 {} \;"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/pub/static -type d -exec chmod 755 {} \;"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/pub/static -type f -exec chmod 644 {} \;"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/pub/media -type d -exec chmod 755 {} \;"
docker-compose exec -u root php-fpm bash -c "find /var/www/html/pub/media -type f -exec chmod 644 {} \;"
PHP Errors: Check the PHP-FPM logs (`/var/log/php-fpm.log` inside the container) and Nginx error logs for specific error messages. Increase PHP memory limits and execution times if necessary.
Composer Issues: Ensure the correct Composer version is used and that it’s compatible with your PHP version. Run `composer install –no-dev` in production environments.
Cron Jobs: Magento’s cron jobs are critical. Ensure your supervisor configuration for cron is correct and that the cron daemon is running. You might need to adjust the cron run interval in `app/etc/env.php` or via `bin/magento cron:install` if running manually.
By systematically addressing these components, you can successfully containerize and orchestrate even complex legacy Magento 2 systems on modern cloud infrastructure, paving the way for improved scalability, manageability, and deployment efficiency.