BLOG POSTS
How to Install Drupal with Docker Compose

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.

Leave a reply

Your email address will not be published. Required fields are marked