
How to Install Drupal with Docker Compose
Setting up Drupal with Docker Compose is one of those tasks that seems straightforward until you hit the first snag, then suddenly you’re three hours deep in permission issues and database connection errors. Docker Compose transforms Drupal deployment from a potentially messy affair involving web servers, PHP configurations, and database setup into a clean, repeatable process that works the same way on your laptop and production servers. This guide walks you through creating a complete Drupal environment using Docker Compose, covering everything from basic setup to handling the quirks that inevitably pop up along the way.
How Docker Compose Simplifies Drupal Deployment
Docker Compose orchestrates multiple containers as a single application stack, which is perfect for Drupal’s multi-service architecture. Instead of manually configuring Apache/Nginx, PHP, MySQL, and dealing with version compatibility issues, you define everything in a single YAML file that spins up your entire environment with one command.
The typical Drupal Docker setup involves three main containers:
- Web container running Apache/Nginx with PHP
- Database container (MySQL or PostgreSQL)
- Optional Redis container for caching
- Optional Elasticsearch container for search functionality
This approach gives you consistent environments across development, staging, and production, plus the ability to easily scale individual components or switch between different versions of services.
Step-by-Step Drupal Installation with Docker Compose
First, create a project directory and set up the basic structure:
mkdir drupal-docker
cd drupal-docker
mkdir drupal-data mysql-data
Create a docker-compose.yml
file with the following configuration:
version: '3.8'
services:
drupal:
image: drupal:10-apache
ports:
- "8080:80"
volumes:
- ./drupal-data:/var/www/html
- ./drupal-data/modules:/var/www/html/modules
- ./drupal-data/profiles:/var/www/html/profiles
- ./drupal-data/themes:/var/www/html/themes
- ./drupal-data/sites:/var/www/html/sites
depends_on:
- mysql
environment:
DRUPAL_DATABASE_HOST: mysql
DRUPAL_DATABASE_PORT: 3306
DRUPAL_DATABASE_NAME: drupal
DRUPAL_DATABASE_USERNAME: drupal
DRUPAL_DATABASE_PASSWORD: drupal_password
networks:
- drupal-network
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root_password
MYSQL_DATABASE: drupal
MYSQL_USER: drupal
MYSQL_PASSWORD: drupal_password
volumes:
- ./mysql-data:/var/lib/mysql
networks:
- drupal-network
phpmyadmin:
image: phpmyadmin/phpmyadmin
ports:
- "8081:80"
environment:
PMA_HOST: mysql
PMA_PORT: 3306
MYSQL_ROOT_PASSWORD: root_password
depends_on:
- mysql
networks:
- drupal-network
networks:
drupal-network:
driver: bridge
Launch the containers:
docker-compose up -d
Wait for all containers to start (usually 30-60 seconds), then navigate to http://localhost:8080
to begin the Drupal installation wizard. The database connection details are:
- Database name: drupal
- Database username: drupal
- Database password: drupal_password
- Host: mysql
- Port: 3306
For advanced configurations, create a custom settings.php
file:
# Create settings directory if it doesn't exist
mkdir -p drupal-data/sites/default
# Copy default settings
docker-compose exec drupal cp /var/www/html/sites/default/default.settings.php /var/www/html/sites/default/settings.php
# Set proper permissions
docker-compose exec drupal chown www-data:www-data /var/www/html/sites/default/settings.php
docker-compose exec drupal chmod 666 /var/www/html/sites/default/settings.php
Production-Ready Configuration
The basic setup works fine for development, but production environments need additional considerations. Here’s an enhanced docker-compose.yml
with Redis caching and better security:
version: '3.8'
services:
drupal:
image: drupal:10-apache
ports:
- "80:80"
volumes:
- drupal-files:/var/www/html/sites/default/files
- ./drupal-config:/var/www/html/sites/default
- ./custom-modules:/var/www/html/modules/custom
- ./custom-themes:/var/www/html/themes/custom
depends_on:
- mysql
- redis
environment:
DRUPAL_DATABASE_HOST: mysql
DRUPAL_DATABASE_PORT: 3306
DRUPAL_DATABASE_NAME: drupal
DRUPAL_DATABASE_USERNAME: drupal
DRUPAL_DATABASE_PASSWORD_FILE: /run/secrets/db_password
secrets:
- db_password
networks:
- drupal-network
restart: unless-stopped
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_password
MYSQL_DATABASE: drupal
MYSQL_USER: drupal
MYSQL_PASSWORD_FILE: /run/secrets/db_password
volumes:
- mysql-data:/var/lib/mysql
secrets:
- mysql_root_password
- db_password
networks:
- drupal-network
restart: unless-stopped
redis:
image: redis:7-alpine
networks:
- drupal-network
restart: unless-stopped
volumes:
drupal-files:
mysql-data:
secrets:
db_password:
file: ./secrets/db_password.txt
mysql_root_password:
file: ./secrets/mysql_root_password.txt
networks:
drupal-network:
driver: bridge
Create the secrets directory and files:
mkdir secrets
echo "your_secure_db_password" > secrets/db_password.txt
echo "your_secure_root_password" > secrets/mysql_root_password.txt
chmod 600 secrets/*.txt
Performance Optimization and Caching
To enable Redis caching in Drupal, install the Redis module and add this configuration to your settings.php
:
$settings['redis.connection']['interface'] = 'PhpRedis';
$settings['redis.connection']['host'] = 'redis';
$settings['redis.connection']['port'] = 6379;
$settings['cache']['default'] = 'cache.backend.redis';
$settings['cache']['bins']['bootstrap'] = 'cache.backend.chainedfast';
$settings['cache']['bins']['discovery'] = 'cache.backend.chainedfast';
$settings['cache']['bins']['config'] = 'cache.backend.chainedfast';
Here’s a performance comparison showing typical response times with different caching strategies:
Configuration | Average Response Time | Memory Usage | Database Queries |
---|---|---|---|
No caching | 850ms | 45MB | 127 |
Database caching | 420ms | 38MB | 34 |
Redis caching | 180ms | 32MB | 12 |
Redis + Page cache | 85ms | 28MB | 3 |
Common Issues and Troubleshooting
Permission problems are the most frequent headache. If you see “Permission denied” errors, fix them with:
# Fix file permissions
docker-compose exec drupal chown -R www-data:www-data /var/www/html/sites/default/files
docker-compose exec drupal find /var/www/html/sites/default/files -type d -exec chmod 755 {} \;
docker-compose exec drupal find /var/www/html/sites/default/files -type f -exec chmod 644 {} \;
# Make settings.php writable during installation, then read-only
docker-compose exec drupal chmod 666 /var/www/html/sites/default/settings.php
# After installation:
docker-compose exec drupal chmod 444 /var/www/html/sites/default/settings.php
Database connection issues usually stem from timing problems. MySQL takes longer to initialize than Drupal expects. Add a health check to your MySQL service:
mysql:
image: mysql:8.0
# ... other config ...
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
timeout: 20s
retries: 10
Memory issues with large sites can be resolved by adjusting PHP memory limits. Create a custom PHP configuration file:
# Create php.ini
echo "memory_limit = 512M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300" > php.ini
# Mount it in docker-compose.yml
volumes:
- ./php.ini:/usr/local/etc/php/conf.d/custom.ini
Comparison with Alternative Deployment Methods
Method | Setup Time | Consistency | Scalability | Maintenance |
---|---|---|---|---|
Manual LAMP Stack | 2-4 hours | Low | Manual | High effort |
Docker Compose | 15-30 minutes | High | Easy horizontal | Medium effort |
Kubernetes | 1-2 days | Very High | Auto-scaling | Complex |
Managed Hosting | 5-10 minutes | Medium | Provider dependent | Low effort |
For most development teams and small to medium production deployments, Docker Compose hits the sweet spot between simplicity and power. VPS hosting works excellently for Docker Compose setups, giving you the control needed for container orchestration without the complexity of managed container services.
Real-World Use Cases and Advanced Configurations
Multi-site Drupal installations benefit significantly from Docker Compose. Here’s a configuration supporting multiple Drupal sites:
version: '3.8'
services:
site1:
image: drupal:10-apache
ports:
- "8080:80"
volumes:
- ./site1-data:/var/www/html
environment:
DRUPAL_DATABASE_HOST: mysql
DRUPAL_DATABASE_NAME: site1_drupal
DRUPAL_DATABASE_USERNAME: site1_user
DRUPAL_DATABASE_PASSWORD: site1_password
depends_on:
- mysql
networks:
- drupal-network
site2:
image: drupal:10-apache
ports:
- "8081:80"
volumes:
- ./site2-data:/var/www/html
environment:
DRUPAL_DATABASE_HOST: mysql
DRUPAL_DATABASE_NAME: site2_drupal
DRUPAL_DATABASE_USERNAME: site2_user
DRUPAL_DATABASE_PASSWORD: site2_password
depends_on:
- mysql
networks:
- drupal-network
mysql:
image: mysql:8.0
environment:
MYSQL_ROOT_PASSWORD: root_password
volumes:
- ./mysql-init:/docker-entrypoint-initdb.d
- mysql-data:/var/lib/mysql
networks:
- drupal-network
volumes:
mysql-data:
networks:
drupal-network:
driver: bridge
Create database initialization scripts in mysql-init/
:
# mysql-init/init.sql
CREATE DATABASE IF NOT EXISTS site1_drupal;
CREATE DATABASE IF NOT EXISTS site2_drupal;
CREATE USER IF NOT EXISTS 'site1_user'@'%' IDENTIFIED BY 'site1_password';
CREATE USER IF NOT EXISTS 'site2_user'@'%' IDENTIFIED BY 'site2_password';
GRANT ALL PRIVILEGES ON site1_drupal.* TO 'site1_user'@'%';
GRANT ALL PRIVILEGES ON site2_drupal.* TO 'site2_user'@'%';
FLUSH PRIVILEGES;
Best Practices and Security Considerations
Always use specific image tags instead of latest
to ensure consistent deployments:
drupal:10.1-apache # Good
drupal:10-apache # Better for receiving patch updates
drupal:latest # Avoid in production
Implement proper backup strategies for both database and files:
#!/bin/bash
# backup.sh
DATE=$(date +%Y%m%d_%H%M%S)
# Backup database
docker-compose exec mysql mysqldump -u root -proot_password drupal > backup_db_$DATE.sql
# Backup files
docker-compose exec drupal tar czf /tmp/files_$DATE.tar.gz /var/www/html/sites/default/files
docker cp $(docker-compose ps -q drupal):/tmp/files_$DATE.tar.gz ./backup_files_$DATE.tar.gz
For production deployments on dedicated servers, consider using Docker Swarm mode for high availability:
docker swarm init
docker stack deploy -c docker-compose.yml drupal-stack
Set up monitoring with container health checks and log aggregation. Tools like Portainer provide excellent web-based management for Docker Compose deployments, especially useful when managing multiple Drupal instances across different environments.
Remember to regularly update your images and rebuild containers to incorporate security patches. The official Drupal Docker image documentation provides detailed information about available tags and security update schedules.

This article incorporates information and material from various online sources. We acknowledge and appreciate the work of all original authors, publishers, and websites. While every effort has been made to appropriately credit the source material, any unintentional oversight or omission does not constitute a copyright infringement. All trademarks, logos, and images mentioned are the property of their respective owners. If you believe that any content used in this article infringes upon your copyright, please contact us immediately for review and prompt action.
This article is intended for informational and educational purposes only and does not infringe on the rights of the copyright owners. If any copyrighted material has been used without proper credit or in violation of copyright laws, it is unintentional and we will rectify it promptly upon notification. Please note that the republishing, redistribution, or reproduction of part or all of the contents in any form is prohibited without express written permission from the author and website owner. For permissions or further inquiries, please contact us.