Sign inSign up

abstechnology/drupal

By abstechnology

•Updated 3 months ago

Image
0

3.2K

abstechnology/drupal repository overview

⁠abstechnology/drupal

Production-ready Drupal 11 image, hardened theo Docker Scout Health Score (7/7 policy pass).

Drupal11.3.9
PHP8.4.21 (FPM, Alpine 3.23)
Drush13.3.5
Composer2.8.4
PECLredis 6.1.0, apcu 5.1.24
Architecturelinux/amd64, linux/arm64
LicenseGPL-2.0-or-later
VendorABS Technology

⁠Companion image

⁠Tag scheme

abstechnology/drupal:11.3.9-php8.4.21-fpm-r1   ← immutable, dùng cho production
abstechnology/drupal:11.3.9                    ← Drupal patch, auto-bump nginx/PHP minor
abstechnology/drupal:latest                    ← chỉ nên dùng để thử

⁠Quick start (5 phút)

⁠1. Tạo folder + 2 file
mkdir my-drupal && cd my-drupal

Tạo file compose.yaml:

name: drupal-prod

x-logging: &default-logging
  driver: json-file
  options: { max-size: "10m", max-file: "5" }

x-restart: &default-restart
  restart: unless-stopped

x-security: &default-security
  security_opt: [no-new-privileges:true]

services:

  data-init:
    image: alpine:3.20
    user: "0:0"
    command:
      - sh
      - -c
      - |
        set -e
        for d in postgres redis drupal/files drupal/private drupal/config backups; do
          mkdir -p "/data/$$d"
        done
        chown -R 70:70    /data/postgres /data/backups
        chown -R 999:1000 /data/redis
        chown -R 82:82    /data/drupal
    volumes:
      - ${DATA_DIR:-./data}:/data
    restart: "no"

  drupal:
    image: ${DRUPAL_IMAGE}
    pull_policy: missing
    <<: [*default-restart, *default-security]
    logging: *default-logging
    user: "www-data:www-data"
    cap_drop: [ALL]
    cap_add: [CHOWN, SETUID, SETGID, DAC_OVERRIDE]
    depends_on:
      data-init: { condition: service_completed_successfully }
      postgres:  { condition: service_healthy }
      redis:     { condition: service_healthy }
    environment:
      DRUPAL_DB_HOST: postgres
      DRUPAL_DB_PORT: 5432
      DRUPAL_DB_NAME: ${POSTGRES_DB}
      DRUPAL_DB_USER: ${POSTGRES_USER}
      DRUPAL_DB_PASSWORD: ${POSTGRES_PASSWORD}
      DRUPAL_DB_DRIVER: pgsql
      DRUPAL_HASH_SALT: ${DRUPAL_HASH_SALT}
      DRUPAL_TRUSTED_HOST_PATTERNS: ${DRUPAL_TRUSTED_HOST_PATTERNS}
      REDIS_HOST: redis
      REDIS_PORT: 6379
      REDIS_PASSWORD: ${REDIS_PASSWORD}
      PHP_MEMORY_LIMIT: 512M
    volumes:
      - ${DATA_DIR:-./data}/drupal/files:/var/www/html/web/sites/default/files
      - ${DATA_DIR:-./data}/drupal/private:/var/www/private
      - ${DATA_DIR:-./data}/drupal/config:/var/www/config
      - drupal_code:/shared
    networks: [backend]
    healthcheck:
      test: ["CMD-SHELL", "php-fpm -t || exit 1"]
      interval: 30s
      timeout: 5s
      retries: 3

  drupal-init:
    image: ${DRUPAL_IMAGE}
    pull_policy: missing
    user: "www-data:www-data"
    depends_on:
      drupal: { condition: service_started }
    entrypoint: ["/bin/sh", "-c"]
    command: >
      "rsync -a --delete --exclude 'web/sites/default/files' /var/www/html/ /shared/"
    volumes:
      - drupal_code:/shared
    networks: [backend]
    restart: "no"

  nginx:
    image: ${NGINX_IMAGE}
    pull_policy: missing
    <<: [*default-restart, *default-security]
    logging: *default-logging
    cap_drop: [ALL]
    cap_add: [CHOWN, SETUID, SETGID, NET_BIND_SERVICE, DAC_OVERRIDE]
    depends_on:
      drupal-init: { condition: service_completed_successfully }
    ports: ["${HTTP_PORT}:80"]
    volumes:
      - drupal_code:/var/www/html:ro
      - ${DATA_DIR:-./data}/drupal/files:/var/www/html/web/sites/default/files:ro
    networks: [backend, frontend]

  postgres:
    image: postgres:16.6-alpine3.20
    <<: [*default-restart, *default-security]
    logging: *default-logging
    cap_drop: [ALL]
    cap_add: [CHOWN, SETUID, SETGID, DAC_OVERRIDE, FOWNER, FSETID]
    depends_on:
      data-init: { condition: service_completed_successfully }
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
      PGDATA: /var/lib/postgresql/data/pgdata
    volumes:
      - ${DATA_DIR:-./data}/postgres:/var/lib/postgresql/data
      - ${DATA_DIR:-./data}/backups:/backups
    networks: [backend]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
      interval: 10s
      retries: 5

  redis:
    image: redis:7.4.1-alpine3.20
    <<: [*default-restart, *default-security]
    logging: *default-logging
    user: "redis:redis"
    cap_drop: [ALL]
    cap_add: [SETUID, SETGID]
    depends_on:
      data-init: { condition: service_completed_successfully }
    command: ["redis-server", "--requirepass", "${REDIS_PASSWORD}",
              "--maxmemory", "256mb", "--maxmemory-policy", "allkeys-lru",
              "--appendonly", "yes"]
    volumes:
      - ${DATA_DIR:-./data}/redis:/data
    networks: [backend]
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      retries: 5

networks:
  frontend: { driver: bridge }
  backend:  { driver: bridge }

volumes:
  drupal_code:

Tạo file .env:

# Image versions (lấy tag từ Docker Hub)
DRUPAL_IMAGE=abstechnology/drupal:11.3.9-php8.4.21-fpm-r1
NGINX_IMAGE=abstechnology/drupal-nginx:11.3.9-r1

# Public HTTP port
HTTP_PORT=8080

# Persistent data root (host folder)
DATA_DIR=./data

# Database
POSTGRES_DB=drupal
POSTGRES_USER=drupal
POSTGRES_PASSWORD='CHANGE_ME_strong_db_password'

# Redis
REDIS_PASSWORD='CHANGE_ME_strong_redis_password'

# Drupal (sinh hash bằng: openssl rand -base64 64 | tr -d '\n')
DRUPAL_HASH_SALT='CHANGE_ME_generate_with_openssl_rand_base64_64'
DRUPAL_TRUSTED_HOST_PATTERNS='^localhost$|^127\.0\.0\.1$'
DRUPAL_SITE_NAME='My Drupal Site'

# Admin user - chỉ dùng cho lần install đầu, sau đó đổi qua UI / drush
DRUPAL_ADMIN_NAME=admin
DRUPAL_ADMIN_PASS='CHANGE_ME_admin_password'
[email protected]

BẮT BUỘC đổi 3 password trên trước khi up. Sinh hash salt:

openssl rand -base64 64 | tr -d '\n' && echo
⁠2. Start stack — Drupal TỰ ĐỘNG cài lần đầu
docker compose up -d

Service drupal-install sẽ tự chạy drush site:install lần đầu (với credentials trong .env), sau đó enable module Redis. Lần sau up sẽ phát hiện DB đã có schema và skip install (idempotent).

Theo dõi tiến trình install (mất ~30s lần đầu):

docker compose logs -f drupal-install
# → "✓ Drupal cài xong" khi xong
⁠3. Truy cập
URL:   http://localhost:${HTTP_PORT}
User:  ${DRUPAL_ADMIN_NAME}            ← lấy từ .env
Pass:  ${DRUPAL_ADMIN_PASS}            ← lấy từ .env

BẮT BUỘC: sau khi đăng nhập lần đầu, đổi password qua /user/1/edit hoặc:

docker compose exec -u www-data drupal drush user:password "${DRUPAL_ADMIN_NAME}" "newSecurePass!"

Sau đó có thể xóa DRUPAL_ADMIN_PASS khỏi .env để đỡ rủi ro lộ.

⁠Common operations

# Xem logs
docker compose logs -f drupal

# Drush
docker compose exec -u www-data drupal drush status
docker compose exec -u www-data drupal drush cr           # clear cache
docker compose exec -u www-data drupal drush en redis -y  # bật module Redis

# Composer (cài contrib module)
docker compose exec -u www-data drupal composer require drupal/webform

# Backup DB
docker compose exec -T postgres pg_dump -U drupal -F c drupal \
  > backups/$(date +%Y%m%d-%H%M%S).dump

# Restore DB
cat backup.dump | docker compose exec -T postgres pg_restore \
  -U drupal -d drupal --clean --if-exists

# Stop / start
docker compose stop
docker compose start
docker compose down            # giữ data
docker compose down -v         # XÓA cả named volume drupal_code

⁠Architecture

       Browser
          │ HTTP :${HTTP_PORT}
          ▼
    ┌──────────────┐  FastCGI :9000   ┌────────────────┐   :5432   ┌──────────┐
    │ drupal-nginx │ ─────────────────►│  drupal        │ ────────► │ postgres │
    │ (web server) │ ◄─response──────  │  (PHP-FPM)     │           └──────────┘
    └──────────────┘                   │  Drupal 11.3.9 │   :6379   ┌──────────┐
                                       └────────────────┘ ────────► │  redis   │
                                                                    └──────────┘

Init containers (chạy 1 lần khi up, exit 0):

data-init       → mkdir + chown ./data/{postgres,redis,drupal,backups}
drupal-install  → drush site:install + drush en redis  (chỉ lần đầu)
drupal-init     → rsync code Drupal sang named volume cho nginx mount

⁠Persistent data

Tất cả data nằm trong ${DATA_DIR} (default ./data/):

data/
├── postgres/      ← DB files (uid 70)
├── redis/         ← AOF persistence (uid 999)
├── drupal/
│   ├── files/     ← public uploads (uid 82, www-data)
│   ├── private/   ← private files
│   └── config/    ← drush cex/cim
└── backups/       ← pg_dump output

Backup folder data/ là backup toàn bộ stack.

⁠Security hardening (Docker Scout 7/7)

  • Non-root user www-data (uid 82)
  • cap_drop: ALL + cap_add minimal
  • no-new-privileges:true
  • SBOM + Provenance attestations
  • No AGPL-3.0 dependencies
  • Auto-scan trong CI: docker scout cves abstechnology/drupal:latest

⁠Source code

github.com/abstechnology/drupal⁠ — Dockerfile, build scripts, CI.

⁠Support

Tag summary

Content type

Image

Digest

sha256:a70e022fb…

Size

207 MB

Last updated

3 months ago

docker pull abstechnology/drupal