[태그:] Redis

  • 자기 PC가 알아서 — 백업·캐시·이미지 압축

    자기 PC가 알아서 — 백업·캐시·이미지 압축

    자기 PC가 알아서 — 백업·캐시·이미지 압축

    셀프호스팅 구축기 4편. 운영은 자동화로 푼다.


    TL;DR

    • 백업: 매일 03:00 mariadb-dump → gzip → Obsidian Vault. 30일 회전. 미니PC 죽어도 글 안전
    • DB 캐시: Redis Object Cache로 반복 쿼리 메모리 캐싱. 페이지 응답 30% 빨라짐
    • 페이지 캐시: WP Super Cache로 정적 HTML 생성. 두 번째 방문자부터 PHP 안 거침
    • 이미지: EWWW로 자동 WebP 변환. 평균 30~70% 용량 ↓
    • 셋업 한 번, 잊는다

    1. 셀프호스팅의 진짜 일은 운영

    처음 셋업은 30분이지만, 그 후가 진짜다. 매일 누가 백업하고, 누가 캐시 비우고, 누가 이미지 압축하나? 사람이 하면 까먹는다. 자동화한다.

    

    2. 백업 — wp-backup.sh

    /home/worker/scripts/wp-backup.sh:

    #!/bin/bash
    set -euo pipefail
    
    BACKUP_DIR="/home/user/문서/Obsidian Vault/KnowledgeVault/Meta/backups/wordpress"
    DATE=$(date +%Y-%m-%d)
    LOG_DIR="/home/worker/logs"
    WEEK=$(date +%Y-W%V)
    LOG_FILE="$LOG_DIR/kimcorp-${WEEK}.log"
    
    mkdir -p "$BACKUP_DIR" "$LOG_DIR"
    
    START=$(date +%s)
    TARGET="$BACKUP_DIR/wp-${DATE}.sql.gz"
    
    # mariadb-dump → gzip
    if sudo docker exec wordpress-db-1 \
       mariadb-dump --single-transaction -u root -p"${DB_ROOT_PW}" wp \
       | gzip > "$TARGET"; then
        SIZE=$(du -h "$TARGET" | cut -f1)
        ELAPSED=$(( $(date +%s) - START ))
        echo "$(date '+%Y-%m-%d %H:%M') | [wp-backup] | OK | ${SIZE} | ${ELAPSED}s" >> "$LOG_FILE"
    else
        echo "$(date '+%Y-%m-%d %H:%M') | [wp-backup] | FAIL-L2 | dump error" >> "$LOG_FILE"
        exit 1
    fi
    
    # 30일 이상 회전
    find "$BACKUP_DIR" -name "wp-*.sql.gz" -mtime +30 -delete
    

    권한: chmod +x. crontab:

    0 3 * * * /home/worker/scripts/wp-backup.sh
    

    매일 새벽 03:00 자동 실행. 첫 백업 검증:

    ls -la "$BACKUP_DIR"
    # wp-2026-05-27.sql.gz  20K
    

    20KB는 콘텐츠 없는 초기 DB. 글 100편 + 이미지 메타 = 보통 500KB~5MB.

    왜 Obsidian Vault에 저장?

    Obsidian Vault는 이미 사장님 노트 시스템 = 자동으로 다른 PC·폰에 동기화됨 (OneDrive·Syncthing 사용 시). 별도 백업 매체 셋업 불필요. 미니PC 죽어도 사장님 노트북·폰에 백업 사본 살아있음.


    3. Redis Object Cache — DB 쿼리 캐싱

    WordPress는 페이지 한 번 표시할 때 평균 50~200개 DB 쿼리를 던진다. 그중 80%가 반복(메뉴·옵션·설정).

    Redis Object Cache가 이 반복 쿼리를 메모리에 캐싱:

    wp config set WP_REDIS_HOST redis --allow-root
    wp config set WP_REDIS_PORT 6379 --allow-root
    wp config set WP_REDIS_PREFIX byeolgorae: --allow-root
    wp config set WP_CACHE true --raw --allow-root
    wp plugin install redis-cache --activate --allow-root
    wp redis enable --allow-root
    

    검증:

    wp redis status --allow-root
    # Status: Connected
    # Client: PhpRedis (v6.x)
    

    체감: 페이지 응답 300ms → 200ms (30% 단축).


    4. WP Super Cache — 페이지 캐시

    Redis가 DB 쿼리 캐시라면, WP Super Cache는 완성된 HTML 자체를 캐싱.

    방문 Redis만 Redis + Super Cache
    첫 방문자 PHP 실행 + Redis 캐시 미스 → DB → 응답 PHP 실행 + 정적 HTML 생성
    두 번째~ 방문자 PHP 실행 + Redis 캐시 히트 → 응답 (~200ms) PHP 안 거침. 정적 파일 직접 응답 (~50ms)

    플러그인 설치 후 활성화는 GUI에서:
    – 설정 → WP Super Cache → Easy 탭
    – “Caching On (Recommended)” 라디오
    – Update Status

    wp-cli로는 활성 못함 (커맨드 미등록). 사장님이 직접 한 번 클릭.

    ⚠ 함정: 로그인된 사용자(사장님)는 페이지 캐시 안 받음 (의도된 동작). 사장님이 글 발행 후 “왜 안 보여?” 할 수 있는데 시크릿 모드로 열어보면 보임.


    5. EWWW Image Optimizer — 이미지 자동 압축

    업로드한 이미지를 자동으로 WebP로 변환 + 무손실 압축.

    형식 같은 사진 용량
    JPEG (원본) 800KB
    WebP (EWWW) 250KB (-69%)
    AVIF (EWWW 유료) 150KB (-81%)

    설치 후 기본 설정 그대로면 OK. 새 이미지 업로드 시 자동 변환. 옛 이미지 일괄 변환은 Bulk Optimize 메뉴.

    체감: 모바일 페이지 LCP(Largest Contentful Paint) 2.5초 → 1.2초.


    6. 종합 — 셋업 한 번, 운영 0

    작업 셋업 시간 이후
    wp-backup.sh + cron 10분 매일 03:00 자동
    Redis Object Cache 3분 영구 자동
    WP Super Cache GUI 활성 1분 영구 자동
    EWWW Image Optimizer 1분 새 이미지 업로드 시 자동

    총 15분 한 번 = 운영 자동화 완료.


    FAQ

    Q. 백업 복원은 어떻게?

    gunzip -c wp-2026-05-30.sql.gz | sudo docker exec -i wordpress-db-1 mariadb -u root -p"${DB_ROOT_PW}" wp
    

    한 줄. 실제 복원은 분기에 한 번 정도 리허설 권장.

    Q. wp_content/uploads 이미지도 백업되나?
    위 스크립트는 DB만. 이미지는 tar -czf uploads-$DATE.tar.gz wp-content/uploads/ 한 줄 추가하면 됨. 또는 Obsidian Vault에 직접 첨부하는 워크플로면 별도 백업 불필요.

    Q. UpdraftPlus 같은 WP 백업 플러그인 쓰면 안 되나?
    가능. 단 WP 안에서 백업 = WP 죽으면 백업도 죽음. 시스템 레벨 cron이 더 안전. UpdraftPlus는 보조로 설치만 해두고 비활성.

    Q. Redis 메모리 차면?
    별고래 트래픽 규모(월 1만 미만)에선 100MB 이하. 메모리 차도 LRU(Least Recently Used)로 자동 정리. 걱정 X.

    Q. WP Super Cache + Cloudflare CDN 중복 아닌가?
    중복 아님. CDN은 정적 파일(이미지·CSS), Super Cache는 HTML. 둘 다 켜는 게 정답.


    다음 편 예고

    5편 — 별고래 분석실: Umami로 방문자 통계. Google Analytics 대신 자체 호스팅. 쿠키 없음 + 사장님이 데이터 소유.


    한 줄 정리

    셋업 15분으로 백업·DB 캐시·페이지 캐시·이미지 압축이 매일 자동. 운영 시간 0, 페이지 30% 빠름, 이미지 70% 작음, 데이터 안전.

  • 한 평짜리 서버 — WordPress를 Docker로 띄우기

    한 평짜리 서버 — WordPress를 Docker로 띄우기

    한 평짜리 서버 — WordPress를 Docker로 띄우기

    셀프호스팅 구축기 2편. 인프라 골격 30분.


    TL;DR

    • WordPress + MariaDB + Redis 세 컨테이너를 docker-compose.yml 한 파일로
    • 호스트 포트 127.0.0.1:8090 만 노출. 외부는 Cloudflare Tunnel(9편)이 라우팅
    • Redis는 Object Cache로 DB 쿼리 캐싱. 무료 + 페이지 응답 30% 빨라짐
    • 함정 5개: 한글 파일명 SCP·healthcheck --connect·named volume 비밀번호 잔류·WP_REDIS_HOST=redis·WORDPRESS_CONFIG_EXTRA X-Forwarded-Proto

    1. 왜 Docker인가 — 호스트 직접 설치 vs 컨테이너

    비교 호스트 직접 (apt install) Docker Compose ⭐
    설치 단순함 apt install apache2 php mariadb-server docker compose up -d
    의존성 충돌 PHP 8.1 vs 8.3 같은 갈등 발생 컨테이너 격리
    미니PC OS 재설치 시 처음부터 다시 compose.yml + 데이터 백업이면 끝
    업데이트 시스템 패키지 매니저 (까다로움) docker compose pull && up -d
    다른 컨테이너 (WP·Umami·cloudflared) 공존 어려움 자연스러움

    미니PC가 이미 다른 서비스를 Docker로 돌리고 있다면 답은 Docker.


    2. 구조 한 장

    

    세 컨테이너가 같은 docker network 안에서 호스트네임(db, redis)으로 통신. 외부 노출은 호스트 127.0.0.1:8090만.


    3. docker-compose.yml 핵심

    services:
      wordpress:
        image: wordpress:php8.3-apache
        restart: unless-stopped
        depends_on:
          db:
            condition: service_healthy
        environment:
          WORDPRESS_DB_HOST: db
          WORDPRESS_DB_USER: wp
          WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
          WORDPRESS_DB_NAME: wp
          WORDPRESS_CONFIG_EXTRA: |
            if (!empty($$_SERVER['HTTP_X_FORWARDED_PROTO']) && $$_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
              $$_SERVER['HTTPS'] = 'on';
            }
        volumes:
          - wordpress_data:/var/www/html
        ports:
          - "127.0.0.1:8090:80"
    
      db:
        image: mariadb:11
        restart: unless-stopped
        environment:
          MARIADB_DATABASE: wp
          MARIADB_USER: wp
          MARIADB_PASSWORD: ${DB_PASSWORD}
          MARIADB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
        volumes:
          - wordpress_db_data:/var/lib/mysql
        healthcheck:
          test: ["CMD", "healthcheck.sh", "--connect"]
          interval: 10s
          timeout: 5s
          retries: 6
          start_period: 30s
    
      redis:
        image: redis:7-alpine
        restart: unless-stopped
        volumes:
          - wordpress_redis_data:/data
    
    volumes:
      wordpress_data:
      wordpress_db_data:
      wordpress_redis_data:
    

    .env 분리:

    DB_PASSWORD=<openssl rand -hex 24>
    DB_ROOT_PASSWORD=<openssl rand -hex 24>
    

    chmod 600 .env. git에 절대 X.


    4. 가동 + 검증

    cd /home/user/wordpress
    docker compose up -d
    docker compose ps
    curl -I http://localhost:8090
    

    응답이 HTTP/1.1 302 (WordPress가 초기 설치 페이지로 리디렉트) 면 성공.

    브라우저로 http://localhost:8090 접속 → 5분 위자드 → 관리자 계정 생성 (이름은 admin 말고 본인 단어. brute force 1차 회피).


    5. Redis Object Cache 활성

    플러그인 redis-cache 설치 후:

    wp config set WP_REDIS_HOST redis --allow-root
    wp config set WP_REDIS_PORT 6379 --allow-root
    wp config set WP_REDIS_PREFIX byeolgorae: --allow-root
    wp config set WP_CACHE true --raw --allow-root
    wp redis enable --allow-root
    

    WP_REDIS_HOST=redis 이게 핵심. Docker 호스트네임. 127.0.0.1이면 자기 컨테이너 안만 봐서 실패.


    6. 함정 — 우리가 실제로 만난 것

    함정 1. 한글·박스 문자 파일 SCP

    docker-compose.ymlminipc_write_file로 작성하다가 한글 주석 + 특수문자 조합에서 SSH escape 깨짐 (bash: -c: 줄 1: '...' 찾는 도중 예상치 못한 파일의 끝).

    → Windows에서 c:\tmp\에 평문 작성 → scp → 미니PC /tmp/sudo cp 경로로 우회.

    함정 2. mariadb healthcheck --innodb-initialized 미지원

    웹 가이드 따라 ["CMD", "healthcheck.sh", "--connect", "--innodb-initialized"] 박았더니 unknown option 에러.

    --connect 만 사용 + start_period: 30s 길게.

    함정 3. .env 비밀번호 바꾼 후 “Database Error”

    DB 비밀번호 한 번 바꾸고 docker compose up -d 했더니 500. 이유: named volume에 옛날 비밀번호가 저장된 MariaDB DB 파일이 살아있음. 새 비밀번호로는 인증 실패.

    docker compose down -v (-v가 volume 같이 제거) → 다시 up -d. 운영 데이터 있으면 절대 안 됨, 초기 셋업 단계에서만.

    함정 4. Redis Connection refused 127.0.0.1:6379

    WP_REDIS_HOST=127.0.0.1 박았다가 컨테이너 안에서 자기 자신 보는 꼴. Redis는 다른 컨테이너.

    WP_REDIS_HOST=redis (Docker network 호스트네임).

    함정 5. HTTPS 리버스 프록시 무한 리다이렉트

    Cloudflare Tunnel이 앞에 서서 HTTPS 종단 후 미니PC엔 HTTP로 forward. WordPress가 자기를 HTTP로 인식 → “HTTPS로 가야 함” 리다이렉트 → 무한 루프.

    WORDPRESS_CONFIG_EXTRA에 X-Forwarded-Proto 처리 (위 yml 참고).


    FAQ

    Q. 메모리 얼마나 쓰나?
    WP(php-fpm 또는 apache) ~200MB, MariaDB ~150MB, Redis ~30MB. 합 ~400MB. 미니PC 8GB RAM이면 여유 충분.

    Q. 디스크 용량은?
    이미지 합 ~1GB. DB는 콘텐츠에 따라 (글 100편 + 이미지 = ~500MB).

    Q. PHP 8.3 말고 8.2면 안 되나?
    WordPress 6.x는 8.2·8.3 둘 다 지원. 8.3이 더 빠름 (JIT 안정화). 최신 권장.

    Q. mariadb 말고 mysql 쓰면?
    mariadb는 mysql 호환 + 라이센스 자유 + Oracle 의존 X. 둘 다 작동. 1인 운영엔 mariadb 추천.

    Q. 백업은 어떻게?
    4편(백업·캐시·이미지)에서 자세히. 매일 03:00 cron으로 mariadb-dump → gzip → Obsidian Vault.


    다음 편 예고

    3편 — AI도 읽으라고: robots.txt와 llms.txt. 봇 차별 대신 AI 크롤러 14종 명시 허용. GEO 시대 콘텐츠 노출 전략.


    한 줄 정리

    WordPress·MariaDB·Redis 세 컨테이너를 docker-compose.yml 한 파일로 묶어 30분에 가동. 호스트 127.0.0.1:8090만 노출, 외부 라우팅은 Cloudflare Tunnel에 위임. 함정은 한글 SCP·healthcheck·volume 잔류·Redis host·X-Forwarded-Proto.