[카테고리:] 셀프호스팅 구축기

  • 도메인 사기 — Cloudflare Registrar

    도메인 사기 — Cloudflare Registrar

    도메인 사기 — Cloudflare Registrar

    셀프호스팅 구축기 6편. 30분이지만 평생 가는 결정.


    TL;DR

    • 도메인 등록 업체 5곳 비교: 가비아·후이즈·GoDaddy·Namecheap·Cloudflare
    • 별고래는 Cloudflare Registrar 선택. 이유: 원가 + DNS + Tunnel + Email 한 계정 통합
    • .org 연 $10. 한국 등록 업체보다 30~50% 저렴
    • 함정: 등록 후 60일 transfer lock, WHOIS 개인정보 보호는 무료

    1. 도메인 이름 정하기 — 별고래 vs Star Whale

    이름 결정에 사흘 걸렸다. 후보 비교 표.

    후보 의미 한국어 SEO 영문 SEO 트레이드마크
    byeolgorae.com 별고래 직역 약함 byeolgorae 발음 어려움 없음
    starwhale.com 영문 직역 약함 starwhale.ai 이미 ML 회사 ⚠ 충돌
    firewhale.com 불 + 고래 (소방 본업 모티프) 약함 firewhale.io 앱사·코인 점유 ⚠ 충돌
    sticknstone.org Stick & Stone (옛 항해사 별점 기구) 깨끗함 깨끗함 없음

    메타포 우선: 브랜드명(별고래)과 도메인(sticknstone)을 분리. About 페이지에서 “Stick & Stone은 작은 돌·막대로 별을 가리키던 옛 항해사 도구. 별고래는 그 별을 향한 깊은 항해” 풀어주면 자연스러움.


    2. 등록 업체 5곳 비교

    

    .org 도메인 1년 가격 비교 (2026-05 기준):

    업체 가격 갱신 특이사항
    가비아 22,000원 22,000원 UI 한국어 친화
    후이즈 19,000원 19,000원 카드 충전식
    GoDaddy $20 → $35 갱신 매년 ↑ 마케팅 푸시 ↑
    Namecheap $13 $15 WHOIS 보호 무료
    Porkbun $9 $11 신생, 안정성 미상
    Cloudflare $10.44 $10.44 원가 (재판매 마진 0)

    Cloudflare는 도메인을 원가로 판매한다. 자기 수익원은 다른 곳 (Workers·Stream·Enterprise). 사장님 같은 1인 운영자에겐 가장 저렴 + 갱신비도 변동 X.


    3. 왜 Cloudflare인가 — 통합 가치

    기능 다른 업체 Cloudflare
    DNS 관리 별도 서비스 (Route 53 등) 통합 무료
    WHOIS 보호 보통 유료 ($10/년) 무료 자동
    Cloudflare Tunnel 외부 라우팅 별도 같은 계정 1클릭
    Email Routing (받기) 별도 G Suite ($6/월) 무료 무제한
    SSL 인증서 자동 (Let’s Encrypt 직접) 자동 (Cloudflare wildcard)
    WAF·DDoS·CDN 별도 ($20+) 같은 도메인에 자동 적용

    별고래는 결국 Cloudflare Tunnel + DNS + WAF + (나중에) Email Routing 다 쓸 거였다. 도메인을 다른 곳에서 사면 nameserver를 Cloudflare로 옮기는 작업이 추가. 같은 곳에서 사면 그 단계 자동.


    4. 구매 과정 — 5분

    1. dash.cloudflare.com → Domains → Buy domain
    2. sticknstone 검색 → .org 선택
    3. 등록자 정보 (이름·이메일·주소)
      – WHOIS는 자동 마스킹됨 (Contact Privacy Inc. 같은 익명)
    4. 카드 결제 ($10.44)
    5. 즉시 활성화. nameserver 자동으로 Cloudflare 가리킴

    다른 업체에서 사면 nameserver를 Cloudflare로 바꾸는 단계 (24~48시간 전파)가 추가. Cloudflare에서 사면 그 시간 0.


    5. 함정

    함정 1. 60일 transfer lock

    도메인 등록 직후 60일은 다른 업체로 이전 불가 (ICANN 규칙). 즉시 Cloudflare 외 다른 곳으로 옮길 일 없으면 무관.

    함정 2. WHOIS 개인정보

    도메인 등록자 정보는 공개가 원칙. 이름·이메일·주소·전화가 WHOIS DB에 등록됨. Cloudflare는 자동 마스킹 (다른 업체는 별도 옵션 $5~10/년). 이메일은 [email protected] 같은 익명 주소가 외부에 보임.

    함정 3. 결제 카드 변경

    카드 만료/변경 시 자동 갱신 실패 → 도메인 expire → 30일 grace period → 회수. 누가 채갈 수 있음. 카드 변경 시 Cloudflare 결제 정보 갱신 필수.

    함정 4. 도메인 가격 변동

    Cloudflare는 원가라 변동 거의 없지만, 신규 TLD(.io·.dev·.app)는 매년 약간씩 인상. .org·.com·.net은 안정적.

    함정 5. 사이트 죽으면 도메인은?

    미니PC 죽거나 cloudflared 종료해도 도메인 자체는 사장님 소유. Cloudflare 대시보드에서 DNS 레코드만 살아있음. 미니PC 복구하면 다시 작동.


    FAQ

    Q. .org가 좋은가? .com은?
    .com이 가장 일반적이지만 좋은 이름이 대부분 점유. .org는 비영리·개인 노트에 자연스러움. SEO 차이 거의 없음 (Google 공식 답변).

    Q. 한글 도메인(별고래.한국)은?
    가능. 단 외국인이 입력 어려움 + 일부 도구 호환성 문제 + 신뢰감 약함. 영문 도메인 + 한국어 콘텐츠 조합이 표준.

    Q. 도메인 + 호스팅 묶음 상품 (가비아 HostingNW 등)은?
    1인 셀프호스팅 미니PC 있으면 무관. 묶음 상품의 호스팅은 공유 호스팅 (다른 사람과 서버 공유). 제어권 적음.

    Q. Cloudflare Email Routing은 어떻게 작동?
    [email protected] → Cloudflare 받음 → 사장님 개인 이메일 ([email protected])로 forward. 무료 무제한. 보낼 때는 별도 SMTP 필요 (Resend·Mailgun 무료티어).

    Q. 도메인 평가 사이트(Estibot 등) 신뢰?
    1인 운영자에겐 무관. 도메인은 사용 가치(내가 쓰기에 좋은가)가 시장 가치(누가 사줄 거)보다 중요.


    다음 편 예고

    7편 — 이름이 어렵다: 사이트명 정하기. “잼잼의 항해일기” → “별고래”로 바꾼 사흘의 고민과 영문 부제 결정.


    한 줄 정리

    도메인은 평생 가는 결정. Cloudflare Registrar = 원가 + DNS + Tunnel + Email + WHOIS 보호 통합 한 계정. .org 연 $10.44. 가비아 대비 절반.

  • AI도 읽으라고 — robots.txt와 llms.txt

    AI도 읽으라고 — robots.txt와 llms.txt

    AI도 읽으라고 — robots.txt와 llms.txt

    셀프호스팅 구축기 3편. SEO 시대가 저물고 GEO 시대가 시작된다. 우리는 어느 쪽?


    TL;DR

    • 미국 인구 31.3%가 2026년 생성형 AI 검색 사용. 83% 쿼리가 사이트 방문 없이 만족 (eMarketer)
    • 옛 방식: robots.txt에 AI 봇 차단. 검색 방어 마인드
    • 새 방식: AI 봇 14종 명시 허용 + llms.txt로 사이트 안내. 인용되는 사이트가 이김
    • 별고래 mu-plugin 한 파일 + /llms.txt 한 파일 = 5분

    1. SEO에서 GEO로 — 무엇이 바뀌었나

    

    핵심 변화:
    트래픽 ↓ — 검색 결과 페이지 안 거치고 AI 답변 한 번에. 사이트 방문 자체가 줄어듦
    인용 ↑ — AI가 답변할 때 “출처: ~”로 사이트 이름 노출
    신뢰 = 인용 횟수 — Perplexity·ChatGPT가 우리 글을 자주 인용하면 그게 새로운 권위

    봇 차단은 자살. 인용 안 되면 존재 안 함이 된다.


    2. 차별이 아니라 허용 — 14종 AI 크롤러

    robots.txt를 mu-plugin으로 동적 생성:

    <?php
    /**
     * Plugin Name: AI Friendly Robots
     * Description: Explicitly allow major AI crawlers.
     */
    add_filter('robots_txt', function ($output, $public) {
        if ($public != '1') return $output;
    
        $output .= "\n# === AI search engines / LLM crawlers (explicitly allowed) ===\n";
        $bots = [
            'GPTBot',           // OpenAI 학습
            'OAI-SearchBot',    // OpenAI 검색
            'ChatGPT-User',     // ChatGPT 실시간 조회
            'ClaudeBot',        // Anthropic 학습
            'Claude-Web',       // Claude.ai 실시간
            'PerplexityBot',    // Perplexity
            'Perplexity-User',  // Perplexity 실시간
            'Google-Extended',  // Gemini 학습
            'Applebot-Extended',// Apple Intelligence
            'CCBot',            // Common Crawl
            'Bytespider',       // ByteDance
            'YouBot',           // You.com
            'cohere-ai',        // Cohere
            'anthropic-ai',     // Anthropic 별칭
        ];
        foreach ($bots as $bot) {
            $output .= "User-agent: $bot\nAllow: /\n\n";
        }
        return $output;
    }, 10, 2);
    

    /var/www/html/wp-content/mu-plugins/ai-robots.php 저장. mu-plugins는 자동 활성.


    3. llms.txt — AI를 위한 사이트맵

    llmstxt.org 표준. AI가 사이트에 들어와서 처음 읽는 파일. “이 사이트는 뭐고 어디에 뭐 있나”를 마크다운으로.

    별고래 /var/www/html/llms.txt 예시:

    # 별고래 (Star Whale)
    
    > 사장님 1인 학습·트레이딩·자동화 항해일지.
    > Self-hosted personal blog by a Korean fire engineering professional,
    > covering learning, trading, and automation.
    
    ## About
    - [Home](https://sticknstone.org/): 메인 페이지
    - [About](https://sticknstone.org/about/): 운영자 소개
    
    ## Topics
    - 트레이딩 — DCA·EDCA·VA·SR 전략 실험과 매매 일지
    - 소방 기술·법령 — NFPC/NFTC 법령 기반 공부 노트
    - AI 자동화 — Claude·hermes·anki-pipe 등 개인 자동화 시스템
    - 셀프호스팅 인프라 — WordPress + Cloudflare Tunnel + Umami
    
    ## RSS / Sitemap
    - [RSS feed](https://sticknstone.org/feed/)
    - [Sitemap](https://sticknstone.org/sitemap_index.xml)
    
    ## Note for LLMs
    이 사이트는 1인 운영자가 직접 학습하며 작성하는 노트입니다.
    인용 시 출처와 함께 표기해주시면 감사하겠습니다.
    

    왜 한국어 + 영어 둘 다?

    • 한국어 → 한국 AI 사용자 (특히 클로바X·뤼튼·코파일럿 한국어 모드)
    • 영어 → 글로벌 AI (ChatGPT·Perplexity·Claude 영문 답변 시 참조)

    같은 파일에 둘 다 박으면 AI가 알아서 적절히 사용.


    4. 어디까지 노출할 것인가 — 차단 vs 허용

    봇 종류 차단 vs 허용 이유
    Google·Bing 검색 봇 ✅ 허용 (기본) 전통 검색 노출
    GPTBot·ClaudeBot 학습 봇 ✅ 허용 미래 AI에 별고래 콘텐츠 들어감
    Perplexity·Claude-Web 실시간 봇 ✅ 허용 답변 인용 시 출처 표시
    스팸 봇·취약점 스캐너 ❌ 차단 (자동) Wordfence가 처리
    비공식 클론 봇 ❌ 차단 mu-plugin에 명시

    원칙: 인용 가능한 봇은 다 허용. 차단으로 가치 보호는 GEO 시대에 역효과.


    5. 검증

    curl https://sticknstone.org/robots.txt | head -30
    curl https://sticknstone.org/llms.txt | head -10
    

    User-agent: GPTBot 부분이 보이면 성공.

    추가로 Google Search Console·Bing Webmaster에 sitemap 제출하면 SEO 측면도 자동 잡힘.


    FAQ

    Q. 콘텐츠가 AI 학습에 쓰이면 손해 아닌가?
    양면. 손해: 콘텐츠 가치 = 학습 데이터로 흡수. 이득: AI가 답변 시 사이트 인용 → 새로운 노출 채널. 개인 학습 노트 블로그라면 이득이 크다. 유료 콘텐츠·뉴스 사이트라면 다른 판단.

    Q. Cloudflare가 AI 봇 차단해주는 기능 켜야 하나?
    Cloudflare는 “AI Crawlers” 토글이 있음. 우리 결정은 정반대 = 허용. 그래서 그 토글은 OFF.

    Q. llms.txt 표준 진짜 쓰나?
    2025년 도입, 2026년 OpenAI·Anthropic·Perplexity 모두 채택 검토. 현재는 실험 단계지만 선점 효과. 5분 작성 비용 대비 이득 크다.

    Q. 한국 AI 검색(클로바X 등)에도 적용되나?
    네이버는 자체 봇 (Yeti), 카카오는 Daum. mu-plugin에 추가 가능. 클로바X는 OpenAI GPT 기반이라 GPTBot 허용으로 이미 처리됨.

    Q. 이거 안 해도 별고래가 자동으로 노출되나?
    robots.txt 기본값(WordPress 기본)은 검색 봇만 허용. AI 봇 명시는 별도. 안 박으면 인용 안 됨.


    다음 편 예고

    4편 — 자기 PC가 알아서: 백업·캐시·이미지 압축. cron·Redis·EWWW로 매일 자동 백업 + 페이지 30% 빨라짐 + 이미지 자동 WebP.


    한 줄 정리

    검색에서 GEO로 시대가 옮겨가는 중. AI 봇 차단하면 존재 없음이 된다. mu-plugin 한 파일 + llms.txt 한 파일로 인용 가능한 사이트가 된다. 5분 작업.

  • 자기 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% 작음, 데이터 안전.

  • 별고래 분석실 — Umami로 방문자 통계

    별고래 분석실 — Umami로 방문자 통계

    별고래 분석실 — Umami로 방문자 통계

    셀프호스팅 구축기 5편. 데이터 주권을 미니PC로 가져오기.


    TL;DR

    • Google Analytics 무료 = 사장님 방문자 데이터를 Google 서버에 줌 + 쿠키 동의 팝업 강제
    • Umami self-hosted = 미니PC에 자체 분석. 쿠키 0, 데이터 본인 소유, GDPR 자동 면제
    • Docker Compose 5분. WP에 트래커 한 줄 자동 주입 = 끝
    • 화면: 방문자 수·인기 글·유입 경로·기기·국가·체류 시간

    1. Google Analytics의 보이지 않는 비용

    항목 Google Analytics 4 Umami self-hosted
    비용 무료 (개인 사용) 무료 (오픈소스)
    데이터 소유 Google 본인 미니PC
    쿠키 동의 팝업 필수 (GDPR/CCPA) 불필요 (쿠키 0)
    페이지 로딩 부하 ~50KB JS ~2KB JS
    사용자 추적 크로스 사이트 (Google 광고 연계) 같은 사이트 내만
    한국 PIPA 대응 까다로움 자동 면제
    설정 복잡도 1시간+ 5분

    Google Analytics는 진짜 비용을 사용자(방문자)에게 외부화한다. 방문자가 사장님 사이트 들어왔다고 Google이 그 데이터 다 받아가는 게 정상인가? 1인 블로그라면 우리가 직접 운영하는 게 옳다.


    2. 구조

    

    방문자 브라우저가 별고래 페이지 받으면 head의 <script defer src="https://analytics.sticknstone.org/script.js" data-website-id="..."> 가 실행 → Umami에 비동기 요청 → Umami가 Postgres에 기록. 페이지 표시는 안 막힘.


    3. Docker Compose

    /home/user/umami/docker-compose.yml:

    services:
      umami:
        image: ghcr.io/umami-software/umami:postgresql-latest
        restart: unless-stopped
        depends_on:
          db:
            condition: service_healthy
        environment:
          DATABASE_URL: postgresql://umami:${DB_PASSWORD}@db:5432/umami
          DATABASE_TYPE: postgresql
          APP_SECRET: ${APP_SECRET}
        ports:
          - "3001:3000"
    
      db:
        image: postgres:16-alpine
        restart: unless-stopped
        environment:
          POSTGRES_DB: umami
          POSTGRES_USER: umami
          POSTGRES_PASSWORD: ${DB_PASSWORD}
        volumes:
          - umami_db:/var/lib/postgresql/data
        healthcheck:
          test: ["CMD-SHELL", "pg_isready -U umami"]
          interval: 5s
          timeout: 5s
          retries: 6
    
    volumes:
      umami_db:
    

    .env:

    APP_SECRET=$(openssl rand -hex 32)
    DB_PASSWORD=$(openssl rand -hex 24)
    

    가동:

    cd /home/user/umami
    docker compose up -d
    curl -I http://localhost:3001  # HTTP/1.1 200
    

    기본 로그인: admin / umami. 즉시 비밀번호 변경.


    4. 별고래 등록 + 트래커 받기

    Umami 대시보드 → Websites → Add Website:

    필드
    Name 별고래 항해일지
    Domain sticknstone.org

    Save → 사이트 클릭 → Edit → Tracking Code:

    <script defer src="https://analytics.sticknstone.org/script.js" data-website-id="1892c870-dd4d-4ce7-9fd6-2635f8ef6220"></script>
    

    5. WP에 자동 주입 — mu-plugin

    매 글마다 사장님이 코드 박는 게 아니라 자동.

    /var/www/html/wp-content/mu-plugins/umami-tracker.php:

    <?php
    /**
     * Plugin Name: Umami Analytics Tracker
     * Description: Injects Umami tracker into front-end pages. Excludes logged-in users.
     */
    if (!defined('ABSPATH')) exit;
    
    add_action('wp_head', function () {
        if (is_admin()) return;
        if (is_user_logged_in()) return;  // 사장님 본인 방문은 제외
        ?>
        <script defer src="https://analytics.sticknstone.org/script.js" data-website-id="1892c870-dd4d-4ce7-9fd6-2635f8ef6220"></script>
        <?php
    }, 100);
    

    is_user_logged_in() 체크 = 사장님이 자기 글 미리보기 한 횟수가 통계에 안 잡힘. 의외로 중요.


    6. 대시보드에서 보이는 것

    화면 데이터
    Overview 일·주·월별 방문자·페이지뷰·세션·체류 시간
    Pages 인기 글 순위
    Referrers 유입 경로 (구글·트위터·다른 사이트)
    Browsers 크롬·사파리·파이어폭스 비율
    OS 윈도우·맥·iOS·안드로이드
    Devices 데스크탑·모바일·태블릿
    Countries 국가별 방문자
    Languages 브라우저 언어
    Events 사장님이 정의한 클릭·다운로드 추적

    5초마다 자동 갱신. Realtime 메뉴 = 지금 이 순간 누가 어느 페이지 보고 있나.


    7. 도메인 연결과 함께 처리해야 하는 이유

    처음 트래커 받으면 URL이 http://192.168.0.x:3001/script.js (LAN IP). 이건:
    – HTTP → 브라우저 Mixed Content 차단
    – LAN IP → 외부 방문자 PC에서 해석 불가

    Umami는 도메인 연결(9편) 후에 활성화. analytics.sticknstone.org로 외부 노출돼야 작동.


    FAQ

    Q. Umami 무료 클라우드 플랜은?
    Umami Cloud는 월 $9~. self-hosted는 무제한 무료. 미니PC 있으면 self-hosted가 답.

    Q. Google Analytics에서 마이그레이션 가능?
    과거 데이터 import 없음. 새 시작. 단 두 도구 동시 가동도 가능 (트래커 둘 다 박음).

    Q. 한국 PIPA(개인정보보호법) 신경 써야 하나?
    Umami는 IP 주소를 저장하지 않음 (해시만), 쿠키 없음. PIPA 면제 대상.

    Q. 모바일 앱은?
    공식 앱 없음. 모바일 웹은 반응형이라 폰에서도 잘 보임.

    Q. 데이터를 CSV로 추출 가능?
    대시보드에서 CSV·JSON export. API도 제공.


    다음 편 예고

    6편 — 도메인 사기: Cloudflare Registrar. 가비아·후이즈 두고 Cloudflare에서 산 이유 (가격·관리 통합·향후 Tunnel 연결).


    한 줄 정리

    Google Analytics의 진짜 비용은 사용자 데이터 외부화. Umami self-hosted = 미니PC에 5분 Docker + WP mu-plugin 자동 주입 = 데이터 주권 + 쿠키 없음 + 같은 정보.

  • 한 평짜리 서버 — 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.