it

Directus CMS Backup Recovery Strategy —

Directus CMS Backup Recovery Strategy —

Directus CMS คืออะไรและทำไมต้องมี Backup Strategy

Directus CMS Backup Recovery Strategy —

Directus เป็น Headless CMS แบบ Open Source ที่สร้าง REST และ GraphQL API จาก Database Schema โดยอัตโนมัติ รองรับ Database หลายตัวทั้ง PostgreSQL, MySQL, MariaDB, SQLite, MS SQL Server และ Oracle ข้อดีของ Directus คือไม่สร้าง custom table แต่ใช้ database ของคุณโดยตรงทำให้ data portability สูงและไม่ lock-in

การมี Backup Strategy ที่ดีสำหรับ Directus มีความสำคัญเพราะ CMS เก็บทั้งข้อมูลเนื้อหา ข้อมูลผู้ใช้ ไฟล์สื่อ (รูปภาพ วิดีโอ เอกสาร) และ configuration ต่างๆ ถ้าข้อมูลสูญหายจะกระทบทั้งเว็บไซต์และ API ที่ downstream application ใช้งาน

Backup Strategy ที่สมบูรณ์สำหรับ Directus ต้องครอบคลุม 3 ส่วนหลักคือ Database Backup ที่เก็บข้อมูลทั้งหมดรวมถึง Directus system tables, File Storage Backup ที่เก็บไฟล์สื่อทั้งหมด และ Configuration Backup ที่เก็บ environment variables และ schema snapshot

แนวทาง 3-2-1 Backup Rule ยังคงเป็นมาตรฐานที่แนะนำคือเก็บ backup อย่างน้อย 3 ชุด บน storage อย่างน้อย 2 ประเภท โดยมีอย่างน้อย 1 ชุดอยู่ offsite เพื่อป้องกัน disaster ที่อาจเกิดขึ้นกับ data center หลัก

ติดตั้ง Directus และตั้งค่า Database

ติดตั้ง Directus ด้วย Docker Compose พร้อม PostgreSQL เป็น production database

เนื้อหาเกี่ยวข้อง — บทความที่เกี่ยวข้อง: data analyst ต้องรู้อะไรบ้าง

# docker-compose.yml สำหรับ Directus Production

version: "3.9"

services:

  postgres:

    image: postgres:16-alpine

    restart: unless-stopped

    environment:

      POSTGRES_DB: directus

      POSTGRES_USER: directus

      POSTGRES_PASSWORD: ""

    volumes:

      - pg_data:/var/lib/postgresql/data

      - ./backups/db:/backups

    healthcheck:

      test: ["CMD-SHELL", "pg_isready -U directus"]

      interval: 10s

      timeout: 5s

      retries: 5

    networks:

      - directus-net



  redis:

    image: redis:7-alpine

    restart: unless-stopped

    command: redis-server --requirepass ""

    volumes:

      - redis_data:/data

    networks:

      - directus-net



  directus:

    image: directus/directus:10.10.0

    restart: unless-stopped

    ports:

      - "8055:8055"

    depends_on:

      postgres:

        condition: service_healthy

      redis:

        condition: service_started

    environment:

      KEY: ""

      SECRET: ""

      DB_CLIENT: pg

      DB_HOST: postgres

      DB_PORT: 5432

      DB_DATABASE: directus

      DB_USER: directus

      DB_PASSWORD: ""

      CACHE_ENABLED: "true"

      CACHE_STORE: redis

      REDIS_HOST: redis

      REDIS_PORT: 6379

      REDIS_PASSWORD: ""

      STORAGE_LOCATIONS: "local, s3"

      STORAGE_LOCAL_ROOT: "/directus/uploads"

      STORAGE_S3_DRIVER: s3

      STORAGE_S3_KEY: ""

      STORAGE_S3_SECRET: ""

      STORAGE_S3_BUCKET: ""

      STORAGE_S3_REGION: "ap-southeast-1"

      ADMIN_EMAIL: "admin@example.com"

      ADMIN_PASSWORD: ""

    volumes:

      - uploads:/directus/uploads

      - ./extensions:/directus/extensions

      - ./snapshots:/directus/snapshots

    networks:

      - directus-net



volumes:

  pg_data:

  redis_data:

  uploads:



networks:

  directus-net:



# สร้าง .env file

# DB_PASSWORD=secure-db-password-here

# REDIS_PASSWORD=secure-redis-password

# DIRECTUS_KEY=random-key-here

# DIRECTUS_SECRET=random-secret-here

# ADMIN_PASSWORD=admin-password-here

# S3_KEY=your-s3-key

# S3_SECRET=your-s3-secret

# S3_BUCKET=directus-uploads



# เริ่มต้นระบบ

# docker compose up -d

สร้าง Schema Snapshot เพื่อเก็บ configuration ของ Directus

# สร้าง Schema Snapshot (เก็บ collections, fields, relations, permissions)

docker compose exec directus npx directus schema snapshot ./snapshots/snapshot-$(date +%Y%m%d-%H%M%S).yaml



# ดูรายการ snapshot

ls -la snapshots/

# snapshot-20260228-150000.yaml

# snapshot-20260227-150000.yaml



# Apply snapshot (สำหรับ restore)

docker compose exec directus npx directus schema apply ./snapshots/snapshot-20260228-150000.yaml



# เปรียบเทียบ schema กับ snapshot

docker compose exec directus npx directus schema diff ./snapshots/snapshot-20260228-150000.yaml

สร้างระบบ Automated Backup ด้วย Cron และ Script

สร้าง backup script ที่เก็บทั้ง database, files และ configuration

แนะนำเพิ่มเติม — ระบบเทรดของ iCafeForex

#!/bin/bash

# backup_directus.sh — Automated Backup Script สำหรับ Directus

set -euo pipefail



# Configuration

BACKUP_DIR="/opt/directus/backups"

RETENTION_DAYS=30

DATE=$(date +%Y%m%d-%H%M%S)

BACKUP_PATH="/"

DB_CONTAINER="directus-postgres-1"

DIRECTUS_CONTAINER="directus-directus-1"

LOG_FILE="/backup.log"



log() {

    echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"

}



# สร้าง directory

mkdir -p ""/{db, files, config}



log "Starting backup: "



# 1. Database Backup (pg_dump)

log "Backing up database..."

docker exec "$DB_CONTAINER" pg_dump -U directus -Fc directus \

    > "/db/directus.dump" 2>> "$LOG_FILE"



DB_SIZE=$(du -sh "/db/directus.dump" | cut -f1)

log "Database backup complete: "



# 2. Schema Snapshot

log "Creating schema snapshot..."

docker exec "$DIRECTUS_CONTAINER" npx directus schema snapshot \

    /directus/snapshots/backup-.yaml 2>> "$LOG_FILE"

cp "$(docker inspect --format='{{range .Mounts}}{{if eq .Destination "/directus/snapshots"}}{{.Source}}{{end}}{{end}}' $DIRECTUS_CONTAINER)/backup-.yaml" \

    "/config/" 2>> "$LOG_FILE" || true



# 3. File Storage Backup

log "Backing up uploaded files..."

docker cp ":/directus/uploads" "/files/"

FILES_SIZE=$(du -sh "/files/" | cut -f1)

log "Files backup complete: "



# 4. Environment Config

log "Backing up configuration..."

cp docker-compose.yml "/config/"

cp .env "/config/env.backup"

cp -r extensions/ "/config/extensions/" 2>/dev/null || true



# 5. สร้าง compressed archive

log "Compressing backup..."

cd "$BACKUP_DIR"

tar czf ".tar.gz" "/"

rm -rf "/"

ARCHIVE_SIZE=$(du -sh ".tar.gz" | cut -f1)

log "Archive created: "



# 6. Checksum

sha256sum ".tar.gz" > ".tar.gz.sha256"



# 7. Cleanup old backups

log "Cleaning up backups older than  days..."

find "$BACKUP_DIR" -name "*.tar.gz" -mtime + -delete

find "$BACKUP_DIR" -name "*.sha256" -mtime + -delete



log "Backup completed successfully: .tar.gz ()"



# 8. ส่ง notification

if [ -n "" ]; then

    curl -s -X POST "$SLACK_WEBHOOK" \

        -H "Content-Type: application/json" \

        -d "{\"text\":\"Directus Backup OK:  (DB: , Files: )\"}"

fi

ตั้ง cron job ให้ backup อัตโนมัติ

# แก้ไข crontab

sudo crontab -e



# Backup ทุกวันตี 2

0 2 * * * /opt/directus/backup_directus.sh >> /var/log/directus-backup.log 2>&1



# Schema snapshot ทุก 6 ชั่วโมง

0 */6 * * * docker exec directus-directus-1 npx directus schema snapshot /directus/snapshots/auto-$(date +\%Y\%m\%d-\%H\%M\%S).yaml



# ตรวจสอบ cron job

crontab -l

sudo systemctl status cron

ตั้งค่า Offsite Backup ไปยัง S3 และ Restic

Directus CMS Backup Recovery Strategy —

ใช้ Restic สำหรับ incremental backup ที่ encrypted ไปยัง S3

# ติดตั้ง Restic

sudo apt install restic -y



# Initialize Restic Repository บน S3

export AWS_ACCESS_KEY_ID="your-access-key"

export AWS_SECRET_ACCESS_KEY="your-secret-key"

export RESTIC_REPOSITORY="s3:s3.amazonaws.com/directus-backups-prod"

export RESTIC_PASSWORD="strong-encryption-password"



restic init



# Backup ไปยัง S3 (incremental)

restic backup /opt/directus/backups/ \

    --tag directus \

    --tag production \

    --exclude "*.log" \

    --verbose



# ดูรายการ snapshots

restic snapshots

# ID        Time                 Host       Tags

# abc123    2026-02-28 02:00:00  prod-srv   directus, production

# def456    2026-02-27 02:00:00  prod-srv   directus, production



# ตรวจสอบ integrity

restic check



# Cleanup old snapshots (เก็บ 7 daily, 4 weekly, 6 monthly)

restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune



# สร้าง script สำหรับ offsite backup อัตโนมัติ

cat > /opt/directus/offsite_backup.sh << 'SCRIPT'

#!/bin/bash

set -euo pipefail



export AWS_ACCESS_KEY_ID="your-access-key"

export AWS_SECRET_ACCESS_KEY="your-secret-key"

export RESTIC_REPOSITORY="s3:s3.amazonaws.com/directus-backups-prod"

export RESTIC_PASSWORD_FILE="/opt/directus/.restic-password"



# Backup

restic backup /opt/directus/backups/ \

    --tag directus --tag "$(date +%A)" \

    --exclude "*.log"



# Prune old snapshots

restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune



# Verify

restic check --read-data-subset=5%



echo "[$(date)] Offsite backup completed"

SCRIPT



chmod +x /opt/directus/offsite_backup.sh



# เพิ่ม cron job

# 0 4 * * * /opt/directus/offsite_backup.sh >> /var/log/restic-backup.log 2>&1

Recovery Procedures สำหรับทุกสถานการณ์

สร้าง Recovery Script สำหรับหลายสถานการณ์

เนื้อหาเกี่ยวข้อง — ทำความเข้าใจ PagerDuty Incident Micro-segmentation

#!/bin/bash # restore_directus.sh — Restore Script สำหรับ Directus set -euo pipefail BACKUP_DIR="/opt/directus/backups" RESTORE_DIR="/tmp/directus-restore" usage() { echo "Usage: $0 [full|db|files|config]" echo " full - Restore ทั้งหมด (default)" echo " db - Restore เฉพาะ database" echo " files - Restore เฉพาะ uploaded files" echo " config - Restore เฉพาะ configuration" exit 1 } [ $# -lt 1 ] && usage BACKUP_FILE="$1" RESTORE_TYPE="" # ตรวจสอบ backup file if [ ! -f "$BACKUP_FILE" ]; then echo "ERROR: Backup file not found: $BACKUP_FILE" exit 1 fi # ตรวจสอบ checksum if [ -f ".sha256" ]; then echo "Verifying checksum..." sha256sum -c ".sha256" || { echo "CHECKSUM FAILED"; exit 1; } fi # Extract backup echo "Extracting backup..." mkdir -p "$RESTORE_DIR" tar xzf "$BACKUP_FILE" -C "$RESTORE_DIR" EXTRACTED=$(ls "$RESTORE_DIR") restore_db() { echo "=== Restoring Database ===" echo "Stopping Directus..." docker compose stop directus echo "Dropping and recreating database..." docker exec directus-postgres-1 psql -U directus -c "DROP DATABASE IF EXISTS directus_old;" docker exec directus-postgres-1 psql -U directus -c "ALTER DATABASE directus RENAME TO directus_old;" docker exec directus-postgres-1 psql -U directus -c "CREATE DATABASE directus OWNER directus;" echo "Restoring from dump..." docker cp "//db/directus.dump" directus-postgres-1:/tmp/ docker exec directus-postgres-1 pg_restore -U directus -d directus /tmp/directus.dump echo "Starting Directus..." docker compose start directus echo "Database restore complete" } restore_files() { echo "=== Restoring Files ===" docker cp "//files/uploads/." directus-directus-1:/directus/uploads/ echo "Files restore complete" } restore_config() { echo "=== Restoring Configuration ===" SNAPSHOT=$(ls "//config/"*.yaml 2>/dev/null | head -1) if [ -n "$SNAPSHOT" ]; then docker cp "$SNAPSHOT" directus-directus-1:/directus/snapshots/restore.yaml docker exec directus-directus-1 npx directus schema apply /directus/snapshots/restore.yaml --yes echo "Schema snapshot applied" fi echo "Config restore complete" } case "$RESTORE_TYPE" in full) restore_db restore_files restore_config ;; db) restore_db ;; files) restore_files ;; config) restore_config ;; *) usage ;; esac # Cleanup rm -rf "$RESTORE_DIR" echo "Restore completed successfully"

Restore จาก Restic (offsite backup)

# ดู snapshots ที่มี

restic snapshots --tag directus



# Restore snapshot ล่าสุด

restic restore latest --target /tmp/restic-restore --tag directus



# Restore snapshot เฉพาะ

restic restore abc123 --target /tmp/restic-restore



# Restore เฉพาะไฟล์บางตัว

restic restore latest --target /tmp/restic-restore \

    --include "/opt/directus/backups/20260228*.tar.gz"



# Mount snapshot เป็น filesystem (สำหรับดูข้อมูลก่อน restore)

restic mount /mnt/restic-mount &

ls /mnt/restic-mount/snapshots/latest/

fusermount -u /mnt/restic-mount

ทดสอบ Disaster Recovery ด้วย Automated Testing

สร้าง automated test ที่ทดสอบกระบวนการ backup และ restore ทุกสัปดาห์

#!/usr/bin/env python3

# dr_test.py — Disaster Recovery Test สำหรับ Directus

import subprocess

import requests

import json

import time

import sys

import os



DIRECTUS_URL = os.getenv("DIRECTUS_URL", "http://localhost:8055")

ADMIN_TOKEN = os.getenv("DIRECTUS_ADMIN_TOKEN", "")

BACKUP_DIR = "/opt/directus/backups"

TEST_DIR = "/tmp/dr-test"

SLACK_WEBHOOK = os.getenv("SLACK_WEBHOOK", "")



class DRTest:

    def __init__(self):

        self.results = []

        self.test_data_id = None



    def log(self, msg):

        print(f"[DR-TEST] {msg}")



    def run_cmd(self, cmd):

        result = subprocess.run(cmd, shell=True, capture_output=True, text=True)

        return result.returncode == 0, result.stdout, result.stderr



    def test_create_test_data(self):

        """สร้างข้อมูลทดสอบ"""

        r = requests.post(f"{DIRECTUS_URL}/items/dr_test", json={

            "test_name": f"DR-Test-{int(time.time())}",

            "test_value": "backup-recovery-validation",

            "timestamp": int(time.time())

        }, headers={"Authorization": f"Bearer {ADMIN_TOKEN}"})

        if r.status_code in [200, 204]:

            self.test_data_id = r.json().get("data", {}).get("id")

            return True, f"Created test data: {self.test_data_id}"

        return False, f"Failed: {r.status_code} {r.text}"



    def test_backup_execution(self):

        """ทดสอบว่า backup script ทำงานได้"""

        ok, out, err = self.run_cmd("/opt/directus/backup_directus.sh")

        if ok:

            latest = max(

                [f for f in os.listdir(BACKUP_DIR) if f.endswith('.tar.gz')],

                key=lambda x: os.path.getmtime(os.path.join(BACKUP_DIR, x))

            )

            size = os.path.getsize(os.path.join(BACKUP_DIR, latest))

            return True, f"Backup OK: {latest} ({size} bytes)"

        return False, f"Backup failed: {err}"



    def test_backup_integrity(self):

        """ตรวจสอบ checksum ของ backup"""

        latest = max(

            [f for f in os.listdir(BACKUP_DIR) if f.endswith('.tar.gz')],

            key=lambda x: os.path.getmtime(os.path.join(BACKUP_DIR, x))

        )

        sha_file = os.path.join(BACKUP_DIR, f"{latest}.sha256")

        if os.path.exists(sha_file):

            ok, _, _ = self.run_cmd(f"cd {BACKUP_DIR} && sha256sum -c {latest}.sha256")

            return ok, f"Checksum {'OK' if ok else 'FAILED'}: {latest}"

        return False, "No checksum file found"



    def test_restore_db(self):

        """ทดสอบ restore database บน test environment"""

        latest = max(

            [f for f in os.listdir(BACKUP_DIR) if f.endswith('.tar.gz')],

            key=lambda x: os.path.getmtime(os.path.join(BACKUP_DIR, x))

        )

        ok, _, err = self.run_cmd(

            f"cd /opt/directus && /opt/directus/restore_directus.sh {BACKUP_DIR}/{latest} db"

        )

        return ok, f"DB Restore {'OK' if ok else 'FAILED'}"



    def test_verify_data_after_restore(self):

        """ตรวจสอบว่าข้อมูลทดสอบยังอยู่หลัง restore"""

        time.sleep(10)  # รอ Directus start

        try:

            r = requests.get(

                f"{DIRECTUS_URL}/items/dr_test/{self.test_data_id}",

                headers={"Authorization": f"Bearer {ADMIN_TOKEN}"},

                timeout=30

            )

            if r.status_code == 200:

                data = r.json().get("data", {})

                if data.get("test_value") == "backup-recovery-validation":

                    return True, "Test data verified after restore"

            return False, f"Data verification failed: {r.status_code}"

        except Exception as e:

            return False, f"Connection error: {e}"



    def run_all_tests(self):

        tests = [

            ("Create Test Data", self.test_create_test_data),

            ("Backup Execution", self.test_backup_execution),

            ("Backup Integrity", self.test_backup_integrity),

            ("Restore Database", self.test_restore_db),

            ("Verify Data", self.test_verify_data_after_restore),

        ]



        self.log("Starting DR Test Suite")

        for name, test_fn in tests:

            try:

                passed, detail = test_fn()

                status = "PASS" if passed else "FAIL"

                self.results.append((name, status, detail))

                self.log(f"  [{status}] {name}: {detail}")

            except Exception as e:

                self.results.append((name, "ERROR", str(e)))

                self.log(f"  [ERROR] {name}: {e}")



        failed = [r for r in self.results if r[1] != "PASS"]

        summary = f"DR Test: {len(self.results)-len(failed)}/{len(self.results)} passed"

        self.log(summary)



        if SLACK_WEBHOOK:

            emoji = "white_check_mark" if not failed else "x"

            requests.post(SLACK_WEBHOOK, json={"text": f":{emoji}: {summary}"})



        return len(failed) == 0



if __name__ == "__main__":

    dr = DRTest()

    success = dr.run_all_tests()

    sys.exit(0 if success else 1)

FAQ คำถามที่พบบ่อย

Q: ควร backup Directus บ่อยแค่ไหน?

แนะนำเพิ่มเติม — ติดตาม XM Signal

A: ขึ้นอยู่กับความถี่ในการอัพเดทเนื้อหา สำหรับเว็บที่อัพเดททุกวันแนะนำ database backup ทุก 6 ชั่วโมง file backup ทุกวัน และ schema snapshot ทุกครั้งที่เปลี่ยน schema สำหรับเว็บที่อัพเดทไม่บ่อย backup วันละ 1 ครั้งเพียงพอ

เนื้อหาเกี่ยวข้อง — อ่านต่อ: Linux eBPF XDP CDN Configuration

Q: pg_dump กับ pg_basebackup ต่างกันอย่างไร?

A: pg_dump สร้าง logical backup ที่เป็น SQL statements หรือ custom format สามารถ restore เฉพาะบาง table ได้ ส่วน pg_basebackup สร้าง physical backup ของทั้ง database cluster เร็วกว่าสำหรับ database ขนาดใหญ่และรองรับ Point-in-Time Recovery (PITR) ด้วย WAL archiving สำหรับ Directus ขนาดเล็กถึงกลาง pg_dump เพียงพอ

Q: Restic กับ Borg Backup ต่างกันอย่างไร?

A: ทั้งคู่เป็น incremental encrypted backup tools แต่ Restic เขียนด้วย Go รองรับ backend หลายตัวทั้ง S3 B2 Azure GCS และ SFTP ส่วน Borg เขียนด้วย Python/C รองรับเฉพาะ local และ SSH ข้อดีของ Borg คือ compression ดีกว่าและ deduplication มีประสิทธิภาพสูงกว่า แต่ Restic ใช้งานง่ายกว่าและรองรับ cloud storage โดยตรง

Q: ควรทดสอบ restore บ่อยแค่ไหน?

เนื้อหาเกี่ยวข้อง — อ่านต่อ: ArgoCD ApplicationSet Zero Downtime Deployment

A: แนะนำทดสอบ restore อย่างน้อยเดือนละ 1 ครั้ง สำหรับระบบที่สำคัญมากควรทดสอบทุกสัปดาห์ด้วย automated DR test script การมี backup ที่ restore ไม่ได้ก็ไม่ต่างจากไม่มี backup เลย

Q: Directus Schema Snapshot คืออะไรและจำเป็นต้อง backup ไหม?

A: Schema Snapshot เก็บ metadata ของ Directus เช่น collections, fields, relations, permissions และ flows ในรูปแบบ YAML จำเป็นต้อง backup เพราะถ้า restore เฉพาะ database แล้วไม่มี snapshot อาจทำให้ Directus UI แสดงผลไม่ถูกต้อง Schema Snapshot ยังใช้สำหรับ migrate configuration ระหว่าง environment ได้อีกด้วย

XM Legend · เทรดเดอร์ & ผู้สอน Forex 13 ปี

ผู้ก่อตั้ง SiamCafe ตั้งแต่ปี 1997 · เทรดเดอร์สาย Forex มากกว่า 13 ปี ได้รับการยกย่องเป็น XM Legend · แบ่งปันความรู้ Forex, ไอที, AI และการเทรด จากประสบการณ์จริงในตลาดจริง