Skip to content

akora/drupal-ansible-automation

Repository files navigation

Drupal 11 DigitalOcean Deployment Automation

📋 Project Overview

This repository provides complete automation for deploying a production-ready Drupal 11 site on DigitalOcean using Ansible. The automation handles everything from server provisioning to security hardening, delivering a fully configured Drupal installation with enterprise-grade security.

🎯 What This Automation Provides

  • 🚀 Complete Drupal 11 Installation: Latest version with Composer dependency management
  • 🔒 Enterprise Security: SSH hardening, firewall configuration, and security headers
  • ⚡ Optimized Performance: Nginx + PHP 8.3 + MySQL 8.0 stack
  • ☁️ Cloudflare Integration: SSL termination and CDN support
  • 🛡️ Production Hardening: UFW firewall, fail2ban protection, and secure configurations
  • 📱 Modern UI: Responsive design with security modules pre-installed

🏗️ Infrastructure Stack

  • OS: Ubuntu 22.04 LTS
  • Web Server: Nginx with security headers
  • Database: MySQL 8.0 with optimized configuration
  • PHP: PHP 8.3 with security hardening
  • CMS: Drupal 11 with security modules
  • Security: UFW firewall + SSH hardening + Security Kit module

⚙️ Initial Setup Requirements

Before running the automation, you need to configure the following placeholder values in your local copy:

🔧 Required Configuration Files

1. Copy Example Files

cp vars.yml.example vars.yml
cp drupal-vars.yml.example drupal-vars.yml  
cp inventory.example inventory

2. Update vars.yml

  • ADMIN_USER: Replace your_admin_username with your desired admin username
  • SSH_PUBLIC_KEY: Replace your-ssh-public-key-here with your actual SSH public key

3. Update drupal-vars.yml

  • DRUPAL_DOMAIN_NAME: Replace your-domain.com with your actual domain
  • DRUPAL_DB_PASSWORD: Replace your_secure_db_password with a strong database password
  • DRUPAL_DB_ROOT_PASSWORD: Replace your_secure_root_password with a strong root password
  • DRUPAL_ADMIN_PASSWORD: Replace your_secure_admin_password with a strong admin password
  • DRUPAL_SITE_NAME: Replace Your Drupal Site Name with your site's name

4. Update inventory

  • your-server: Replace with your server hostname
  • YOUR_SERVER_IP: Replace with your DigitalOcean droplet's IP address
  • your-private-key: Replace with the path to your SSH private key

🔑 SSH Key Setup

# Generate SSH key pair if you don't have one
ssh-keygen -t ed25519 -C "your-email@example.com"

# Add public key to DigitalOcean
doctl compute ssh-key import your-key-name --public-key-file ~/.ssh/id_ed25519.pub

⚠️ Security Notes

  • Never commit the actual configuration files (vars.yml, drupal-vars.yml, inventory) to version control
  • Use strong passwords (20+ characters) for all password fields
  • Ensure your SSH private key is properly secured with appropriate file permissions (chmod 600)

Initial Setup (New Droplet)

Get the IP address of the current / old droplet

doctl compute droplet list --format ID,Name,PublicIPv4,Status

Remove old IP from ~/.ssh/known_hosts

ssh-keygen -R <old IP>

Get the name and ID of the SSH key

doctl compute ssh-key list

Destroy old VM

doctl compute droplet delete homedevbox-ghost

Provision new VM

doctl compute droplet create homedevbox-drupal \
  --image ubuntu-22-04-x64 \
  --size s-1vcpu-2gb \
  --region fra1 \
  --ssh-keys 36:d2:d8:a4:72:45:62:7c:75:3c:f4:b8:d5:a8:43:dd \
  --wait

Check provisioned VM to get the new IP address

doctl compute droplet list --format ID,Name,PublicIPv4,Status

Add new IP to ~/.ssh/config

sed -i '' 's/<old IP>/<new IP>/' ~/.ssh/config
Host your-server
    HostName <new IP>  
    User your_admin_user
    IdentityFile ~/.ssh/your-private-key

Update inventory with new IP

[droplets]
your-server ansible_host=<new IP> ansible_python_interpreter=/usr/bin/python3 ansible_ssh_private_key_file=~/.ssh/your-private-key

🚀 PHASE 1: Fresh Installation (Root Access)

Note: These commands connect as root user for initial setup

Test Connection

ansible all -i inventory -m ping --private-key ~/.ssh/your-private-key

Full Deployment Sequence

1. Baseline Setup (User + System)

ansible-playbook -i inventory playbooks/baseline.yml --private-key ~/.ssh/your-private-key

2. Install MySQL Database

ansible-playbook -i inventory playbooks/mysql.yml --private-key ~/.ssh/your-private-key

3. Install PHP 8.3 Stack

ansible-playbook -i inventory playbooks/php.yml --private-key ~/.ssh/your-private-key

4. Install Nginx Web Server

ansible-playbook -i inventory playbooks/nginx.yml --private-key ~/.ssh/your-private-key

5. Install Drupal 11

ansible-playbook -i inventory playbooks/drupal.yml --private-key ~/.ssh/your-private-key

6. Configure Firewall Security

ansible-playbook -i inventory playbooks/firewall.yml --private-key ~/.ssh/your-private-key

7. Harden SSH Security (⚠️ FINAL STEP)

ansible-playbook -i inventory playbooks/ssh-security.yml --private-key ~/.ssh/your-private-key

⚠️ WARNING: After SSH hardening, root login is disabled. Use Phase 2 commands below.


🔒 PHASE 2: Post-Hardening Operations (User Access)

Note: After SSH hardening, all commands must use -u your_admin_user (root login disabled)

Test Connection (Post-Hardening)

ansible all -i inventory -m ping --private-key ~/.ssh/your-private-key -u your_admin_user

Site Management Commands

Get Drupal Status

ansible all -i inventory -m shell -a "/var/www/drupal/vendor/bin/drush status" --private-key ~/.ssh/your-private-key -u your_admin_user

Clear Drupal Cache

ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush cache:rebuild" --private-key ~/.ssh/your-private-key -u your_admin_user

List Installed Modules

ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush pm:list --type=module" --private-key ~/.ssh/your-private-key -u your_admin_user

List Installed Themes

ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush pm:list --type=theme" --private-key ~/.ssh/your-private-key -u your_admin_user

Module Management

Install a Module (e.g., Pathauto)

# Download via Composer (requires file ownership)
ansible all -i inventory -m shell -a "cd /var/www/drupal && composer require drupal/pathauto" --private-key ~/.ssh/your-private-key -u your_admin_user --become-user your_admin_user

# Enable via Drush
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush pm:install pathauto -y" --private-key ~/.ssh/your-private-key -u your_admin_user

Install Popular Modules

# Admin Toolbar (Enhanced admin navigation)
ansible all -i inventory -m shell -a "cd /var/www/drupal && composer require drupal/admin_toolbar" --private-key ~/.ssh/your-private-key -u your_admin_user --become-user your_admin_user
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush pm:install admin_toolbar admin_toolbar_tools -y" --private-key ~/.ssh/your-private-key -u your_admin_user

# Token (Provides token replacement functionality)
ansible all -i inventory -m shell -a "cd /var/www/drupal && composer require drupal/token" --private-key ~/.ssh/your-private-key -u your_admin_user --become-user your_admin_user
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush pm:install token -y" --private-key ~/.ssh/your-private-key -u your_admin_user

# Devel (Development tools)
ansible all -i inventory -m shell -a "cd /var/www/drupal && composer require drupal/devel" --private-key ~/.ssh/your-private-key -u your_admin_user --become-user your_admin_user
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush pm:install devel -y" --private-key ~/.ssh/your-private-key -u your_admin_user

Theme Management

Install a Theme (e.g., Bootstrap)

# Download via Composer
ansible all -i inventory -m shell -a "cd /var/www/drupal && composer require drupal/bootstrap" --private-key ~/.ssh/your-private-key -u your_admin_user --become-user your_admin_user

# Enable theme
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush theme:enable bootstrap -y" --private-key ~/.ssh/your-private-key -u your_admin_user

# Set as default theme (optional)
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush config:set system.theme default bootstrap -y" --private-key ~/.ssh/your-private-key -u your_admin_user

Security Enhancements

Security Measures (Already Implemented)

VM Level Security

  • SSH Key Authentication: Password authentication disabled (VM level)
  • Firewall Protection: UFW configured with minimal open ports (VM level)
  • Domain-Only Access: IP access blocked, redirects to domain (VM level - Nginx)

Drupal Level Security

  • Strong Password Policy: Login Security module enforces complexity (Drupal level)
  • Failed Login Protection: Login Security blocks brute force attempts (Drupal level)
  • Auto Logout: Automated Logout module for idle sessions (Drupal level)
  • Security Headers: SecKit module adds XSS/CSRF protection (Drupal level)
  • Update Monitoring: Update Status module tracks security patches (Drupal level)

External Service Security

  • HTTPS Termination: Cloudflare provides SSL/TLS encryption (External service)

Additional Security Recommendations

  1. Strong Admin Password: Use a password manager with 20+ character passwords
  2. Limited Admin Access: Only log in when necessary, log out immediately
  3. Regular Updates: Monitor and apply security updates promptly
  4. Access Logs: Monitor /var/log/nginx/drupal_access.log for suspicious activity
  5. Backup Strategy: Regular automated backups of database and files

System Maintenance

Update Drupal Core

ansible all -i inventory -m shell -a "cd /var/www/drupal && composer update drupal/core-recommended --with-dependencies" --private-key ~/.ssh/your-private-key -u your_admin_user --become-user your_admin_user
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush updatedb -y" --private-key ~/.ssh/your-private-key -u your_admin_user
ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush cache:rebuild" --private-key ~/.ssh/your-private-key -u your_admin_user

Database Backup

ansible all -i inventory -m shell -a "cd /var/www/drupal && vendor/bin/drush sql:dump --gzip --result-file=/tmp/drupal-backup-$(date +%Y%m%d-%H%M%S).sql.gz" --private-key ~/.ssh/your-private-key -u your_admin_user

📋 Command Pattern Summary

Phase 1 (Fresh Install): ansible-playbook ... --private-key ~/.ssh/your-private-key

Phase 2 (Post-Hardening): ansible ... --private-key ~/.ssh/your-private-key -u your_admin_user

For Composer operations: ansible ... --private-key ~/.ssh/your-private-key -u your_admin_user --become-user your_admin_user

⚠️ Critical Composer Note

Problem: Using only -u your_admin_user causes Composer to run as root, triggering "Do not run Composer as root" warnings that cause commands to hang waiting for user input.

Solution: Always use --become-user your_admin_user for Composer commands to ensure proper user context and prevent hanging.

Example:

# ❌ WRONG - Will hang
ansible ... -u your_admin_user -m shell -a "composer require drupal/module"

# ✅ CORRECT - Works properly  
ansible ... -u your_admin_user --become-user your_admin_user -m shell -a "composer require drupal/module"

About

Production-ready Drupal 11 deployment automation for DigitalOcean using Ansible

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages