Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
08e1bf2
feat: Add workflow to test Drupal installations
hussainweb Feb 14, 2026
be8810e
chore: Update Docker build and test workflows
hussainweb Feb 14, 2026
60e67b1
ci: Update Docker Compose installation
hussainweb Feb 14, 2026
b9af63e
ci: Install rsync and use it for project setup in Drupal workflow
hussainweb Feb 14, 2026
6bc859a
ci: Improve Drupal test workflow checks
hussainweb Feb 14, 2026
612fa85
ci: Update Drupal test workflow for improved logging and setup
hussainweb Feb 14, 2026
903435f
ci: Add PHP error display for debugging in Drupal workflows
hussainweb Feb 15, 2026
2f04d87
ci: Simplify Drupal installation ownership in test workflow
hussainweb Feb 15, 2026
4a5f703
ci: Set correct ownership for drupal project files
hussainweb Feb 15, 2026
a32b496
ci: improve log output in workflows
hussainweb Feb 15, 2026
d072d63
ci: Add timeouts and improve log collection in test workflow
hussainweb Feb 15, 2026
6b93b3b
chore: Adjust Drupal installation and file permissions
hussainweb Feb 15, 2026
baa0c5b
ci: ensure web/sites/default/files is writable during Drupal installa…
hussainweb Feb 15, 2026
4cf049f
ci: Remove apache-trixie specific chown command
hussainweb Feb 15, 2026
5d90deb
fix: Relax Drupal file permissions in CI
hussainweb Feb 15, 2026
1e9b5ce
ci: Adjust Docker build and push conditions
hussainweb Feb 15, 2026
867bbb2
ci: Simplify Drupal container file permissions
hussainweb Feb 15, 2026
1a8803a
fix(ci): adapt drupal installation permission logic from working script
hussainweb Feb 15, 2026
3325d39
refactor(ci): adopt test structure and scripts from test-docker-drupa…
hussainweb Feb 15, 2026
44cb7f2
ci: Refactor Docker Compose configuration
hussainweb Feb 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 18 additions & 6 deletions .github/workflows/docker-buildx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ on:
workflow_dispatch:
schedule:
- cron: "5 4 1,15 * *"
push:
branches: [main]
pull_request:
workflow_run:
workflows: ["Test Drupal Installation"]
branches: [main]
types:
- completed

env:
DOCKERFILE_DIR: php8
Expand All @@ -17,11 +18,18 @@ concurrency:

jobs:
buildx:
if: github.event_name != 'workflow_run' || github.event.workflow_run.conclusion == 'success'
runs-on: ubuntu-latest
strategy:
matrix:
php_version: ["8.2", "8.3", "8.4", "8.5"]
variant: ["apache-trixie", "apache-bookworm", "fpm-alpine", "frankenphp-trixie"]
variant:
[
"apache-trixie",
"apache-bookworm",
"fpm-alpine",
"frankenphp-trixie",
]
steps:
- name: Checkout
uses: actions/checkout@v6
Expand All @@ -42,7 +50,9 @@ jobs:
with:
username: hussainweb
password: ${{ secrets.DOCKERHUB_TOKEN }}
if: github.event_name != 'pull_request'
if: |
(github.event_name == 'workflow_run' && github.event.workflow_run.head_branch == 'main') ||
(github.event_name != 'workflow_run' && github.ref == 'refs/heads/main')
- name: Set Dockerfile directory
if: ${{ matrix.php_version == '8.2' || matrix.php_version == '8.3' || matrix.php_version == '8.4' || matrix.php_version == '8.5' }}
run: echo "DOCKERFILE_DIR=php8" >> $GITHUB_ENV
Expand Down Expand Up @@ -72,7 +82,9 @@ jobs:
uses: docker/build-push-action@v6
with:
context: ${{ env.DOCKERFILE_DIR }}/${{ matrix.variant }}/
push: ${{ github.event_name != 'pull_request' }}
push: |
${{ (github.event_name == 'workflow_run' && github.event.workflow_run.head_branch == 'main') ||
(github.event_name != 'workflow_run' && github.ref == 'refs/heads/main') }}
tags: ${{ steps.tags.outputs.tags }}
build-args: |
PHP_VERSION=${{ matrix.php_version }}
Expand Down
92 changes: 92 additions & 0 deletions .github/workflows/test-drupal.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Test Drupal Installation

on:
pull_request:
push:
branches:
- main

jobs:
test-drupal:
name: Test on ${{ matrix.variant }} (PHP ${{ matrix.php_version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php_version: ["8.4", "8.5"]
variant: ["apache-trixie", "fpm-alpine", "frankenphp-trixie"]

steps:
- uses: actions/checkout@v4

- name: Build Docker image
run: |
docker build \
--build-arg PHP_VERSION=${{ matrix.php_version }} \
-t drupal-test:${{ matrix.php_version }}-${{ matrix.variant }} \
./php8/${{ matrix.variant }}

- name: Create test directory structure
run: |
mkdir -p drupal-root
mkdir -p test-results

- name: Download Drupal
run: |
composer create-project drupal/recommended-project drupal-root --no-interaction --no-dev
cd drupal-root
composer require drush/drush --no-interaction

- name: Set up environment variables
run: |
echo "PHP_VERSION=${{ matrix.php_version }}" >> $GITHUB_ENV
echo "VARIANT=${{ matrix.variant }}" >> $GITHUB_ENV
if [ "${{ matrix.variant }}" = "frankenphp-trixie" ]; then
echo "WEB_ROOT=/app" >> $GITHUB_ENV
else
echo "WEB_ROOT=/var/www/html" >> $GITHUB_ENV
fi
if [ "${{ matrix.variant }}" = "fpm-alpine" ]; then
echo "COMPOSE_FILE=tests/docker-compose.fpm.yml" >> $GITHUB_ENV
else
echo "COMPOSE_FILE=tests/docker-compose.yml" >> $GITHUB_ENV
fi
echo "CONTAINER_NAME=drupal" >> $GITHUB_ENV

- name: Start Docker containers
run: |
docker compose up -d
sleep 10
docker compose ps

- name: Install Drupal
run: |
chmod +x tests/install-drupal.sh
# The script uses 'docker compose exec -T drupal', which matches our service name 'drupal'
./tests/install-drupal.sh ${{ matrix.variant }} "11.x"

- name: Run Drupal verification tests
run: |
chmod +x tests/verify-drupal.sh
./tests/verify-drupal.sh ${{ matrix.variant }}

- name: Capture logs
if: always()
run: |
echo "=== Docker Compose Logs ==="
docker compose logs
echo "=== Container Status ==="
docker compose ps
docker compose logs > test-results/docker-logs-${{ matrix.variant }}.txt

- name: Upload test results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results-${{ matrix.php_version }}-${{ matrix.variant }}
path: test-results/
if-no-files-found: ignore

- name: Clean up
if: always()
run: docker compose down -v
16 changes: 16 additions & 0 deletions tests/docker-compose.fpm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
services:
drupal:
image: drupal-test:${PHP_VERSION}-${VARIANT}
container_name: drupal
volumes:
- ../drupal-root:/var/www/html
nginx:
image: nginx:alpine
container_name: nginx
ports:
- "8080:80"
volumes:
- ../drupal-root:/var/www/html
- ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
depends_on:
- drupal
8 changes: 8 additions & 0 deletions tests/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
services:
drupal:
image: drupal-test:${PHP_VERSION}-${VARIANT}
container_name: drupal
volumes:
- ../drupal-root:${WEB_ROOT}
ports:
- "8080:80"
87 changes: 87 additions & 0 deletions tests/install-drupal.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
#!/bin/bash
set -e

VARIANT=$1
DRUPAL_VERSION=$2

echo "===================================="
echo "Installing Drupal ${DRUPAL_VERSION} on ${VARIANT}"
echo "===================================="

# Use service name instead of container name
SERVICE="drupal"

# Set the web root path based on variant
if [[ "$VARIANT" == *"frankenphp"* ]]; then
WEBROOT="/app"
else
WEBROOT="/var/www/html"
fi

# Wait for the container to be fully ready with health checks
echo "Waiting for container to be ready..."
for i in {1..12}; do
if docker compose exec -T $SERVICE sh -c 'exit 0' 2>/dev/null; then
echo "Container is ready!"
break
fi
if [ $i -eq 12 ]; then
echo "Container failed to become ready after 60 seconds"
docker compose ps
docker compose logs
exit 1
fi
echo "Waiting for container... ($i/12)"
sleep 5
done

# Check if Drupal is already installed
INSTALLED=$(docker compose exec -T $SERVICE sh -c "if [ -f ${WEBROOT}/web/sites/default/settings.php ] && grep -q 'database' ${WEBROOT}/web/sites/default/settings.php 2>/dev/null; then echo 'yes'; else echo 'no'; fi" || echo "no")

if [ "$INSTALLED" = "yes" ]; then
echo "Drupal appears to be already installed. Skipping installation."
exit 0
fi

# Set proper permissions
echo "Setting up permissions..."
docker compose exec -T $SERVICE sh -c "mkdir -p ${WEBROOT}/web/sites/default/files && chmod -R 777 ${WEBROOT}/web/sites/default/files"
docker compose exec -T $SERVICE sh -c "chmod 777 ${WEBROOT}/web/sites/default"

# Install Drupal using drush with SQLite database file
echo "Installing Drupal using drush with SQLite..."
docker compose exec -T $SERVICE sh -c "cd ${WEBROOT} && vendor/bin/drush site:install minimal \
--db-url="sqlite://localhost/sites/default/files/.ht.sqlite" \
--site-name="Drupal Test Site" \
--account-name=admin \
--account-pass=admin \
--yes \
--no-interaction"

# Verify installation
echo "Verifying Drupal installation..."
DRUSH_STATUS=$(docker compose exec -T $SERVICE sh -c "cd ${WEBROOT} && vendor/bin/drush status --format=json" || echo "{}")

echo "Drush status output:"
echo "$DRUSH_STATUS"

# Check if bootstrap was successful
if echo "$DRUSH_STATUS" | grep -q "bootstrap"; then
echo "✓ Drupal installation completed successfully"
else
echo "✗ Drupal installation may have issues"
exit 1
fi

# Set permissions back to safer values but keep files directory writable
echo "Securing permissions..."
# sites/default should not be writable by web server (755)
docker compose exec -T $SERVICE sh -c "chmod 755 ${WEBROOT}/web/sites/default"
# Keep files directory fully writable (777) for testing - SQLite needs directory write access
docker compose exec -T $SERVICE sh -c "chmod -R 777 ${WEBROOT}/web/sites/default/files"

echo "===================================="
echo "Drupal installation complete"
echo "Admin user: admin"
echo "Admin pass: admin"
echo "===================================="
129 changes: 129 additions & 0 deletions tests/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
server {
listen 80;
server_name localhost;
root /var/www/html/web;

index index.php index.html index.htm;

# Logging
access_log /var/log/nginx/drupal_access.log;
error_log /var/log/nginx/drupal_error.log;

# Drupal specific configurations
location = /favicon.ico {
log_not_found off;
access_log off;
}

location = /robots.txt {
allow all;
log_not_found off;
access_log off;
}

# Very rarely should these ever be accessed outside of your lan
location ~* \.(txt|log)$ {
deny all;
}

location ~ \..*/.*\.php$ {
return 403;
}

location ~ ^/sites/.*/private/ {
return 403;
}

# Block access to scripts in site files directory
location ~ ^/sites/[^/]+/files/.*\.php$ {
deny all;
}

# Allow "Well-Known URIs" as per RFC 5785
location ~* ^/.well-known/ {
allow all;
}

# Block access to "hidden" files and directories whose names begin with a
# period. This includes directories used by version control systems such
# as Subversion or Git to store control files.
location ~ (^|/)\. {
return 403;
}

location / {
# try_files $uri @rewrite; # For Drupal <= 6
try_files $uri /index.php?$query_string; # For Drupal >= 7
}

location @rewrite {
rewrite ^/(.*)$ /index.php?q=$1;
}

# Don't allow direct access to PHP files in the vendor directory.
location ~ /vendor/.*\.php$ {
deny all;
return 404;
}

# Protect files and directories from prying eyes.
location ~* \.(engine|inc|install|make|module|profile|po|sh|.*sql|theme|twig|tpl(\.php)?|xtmpl|yml)(~|\.sw[op]|\.bak|\.orig|\.save)?$|^(\.(?!well-known).*|Entries.*|Repository|Root|Tag|Template|composer\.(json|lock)|web\.config)$|^#.*#$|\.php(~|\.sw[op]|\.bak|\.orig|\.save)$ {
deny all;
return 404;
}

location ~ '\.php$|^/update.php' {
fastcgi_split_path_info ^(.+?\.php)(|/.*)$;

# Ensure the php file exists. Mitigates CVE-2019-11043
try_files $fastcgi_script_name =404;

# Block httpoxy attacks. See https://httpoxy.org/.
fastcgi_param HTTP_PROXY "";

fastcgi_pass drupal:9000;
fastcgi_index index.php;

include fastcgi_params;

# SCRIPT_FILENAME parameter is used for PHP FPM determining
# the script name. If it is not set in fastcgi_params file,
# i.e. /etc/nginx/fastcgi_params or in the parent contexts,
# please comment off following line:
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param QUERY_STRING $query_string;

fastcgi_intercept_errors on;

# PHP 7 socket
# fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;

# PHP 7 TCP
# fastcgi_pass 127.0.0.1:9000;

fastcgi_buffer_size 128k;
fastcgi_buffers 4 256k;
fastcgi_busy_buffers_size 256k;
fastcgi_temp_file_write_size 256k;
fastcgi_read_timeout 240;
}

# Fighting with Styles? This little gem is amazing.
location ~ ^/sites/.*/files/styles/ {
try_files $uri @rewrite;
}

# Handle private files through Drupal. Private file's path can come
# with a language prefix.
location ~ ^(/[a-z\-]+)?/system/files/ {
try_files $uri /index.php?$query_string;
}

# Enforce clean URLs
# Removes index.php from urls like www.example.com/index.php/my-page --> www.example.com/my-page
# Could be done with 301 for permanent or other redirect codes.
if ($request_uri ~* "^(.*/)index\.php/(.*)") {
return 307 $1$2;
}
}
Loading