[Lap 05] Dockerfile Pre-baking 기반 불변 워드프레스 완성

🏁 LAP 05 / 07 · ⏱️ 8 min read

“서버리스 컨테이너는 언제든 내려가고 새로 생성될 수 있는 불변(Immutable) 환경이어야 합니다.”
512MB RAM의 Cloud Run 컨테이너와 1GB RAM의 무료 DB VM 위에서 워드프레스를 가볍고 빠르게 운영하려면, 애플리케이션 계층에서도 불필요한 무게를 덜어내야 합니다. 특히 컨테이너가 재시작될 때마다 관리자 화면에서 설치한 테마와 플러그인이 사라지는 무상태(Stateless) 환경의 특성을 아키텍처적으로 해결해야 했습니다.
이번 랩에서는 제가 왜 화려한 페이지 빌더와 로컬 캐시 플러그인을 배제했는지, 그리고 필수 테마와 플러그인을 Dockerfile 빌드 시점에 이미지로 구워 넣어(Pre-baking) 언제나 동일한 상태를 보장하도록 만든 과정을 정리합니다.


1. 들어가며: 왜 화려한 페이지 빌더와 로컬 캐시 플러그인을 쓰지 않았는가?

워드프레스를 처음 구축할 때 많은 분이 드래그 앤 드롭으로 화면을 쉽게 꾸밀 수 있는 Elementor(엘리멘터)나 Divi 같은 대형 페이지 빌더를 설치하곤 합니다. 하지만 제한된 리소스 안에서 고성능을 내야 하는 아키텍트의 시각으로 내부 동작을 살펴보면 꽤 큰 비용을 치러야 합니다.

제가 이번 프로젝트에서 대형 페이지 빌더와 디스크 캐시 플러그인을 완전히 배제한 이유는 크게 두 가지입니다.

⚠️ 이유 1: 대형 페이지 빌더의 과도한 DOM 중첩과 메모리 사용량

  • 깊어지는 DOM 트리(DOM Bloat): 엘리멘터 같은 빌더는 버튼 하나, 문단 하나를 배치할 때도 .elementor-section > .elementor-container > .elementor-column > .elementor-widget-wrap처럼 6~8단계의 중첩된 <div> 태그를 생성합니다. 여기에 수 메가바이트에 달하는 CSS와 JS 파일이 함께 로드되면서 브라우저 렌더링 속도를 늦춥니다.
  • PHP 메모리 및 DB 쿼리 부담: 페이지를 렌더링할 때마다 수많은 PHP 훅(Hook)과 wp_options 메타 쿼리를 실행하며 컨테이너 메모리를 128MB~256MB씩 소비합니다. 512MB 컨테이너와 1GB 무료 DB VM 환경에서는 불필요한 오버헤드입니다.

⚠️ 이유 2: 서버리스 환경에서 디스크 기반 캐시 플러그인이 일으키는 문제

워드프레스 속도를 높이려고 흔히 설치하는 WP Super Cache나 W3 Total Cache 같은 로컬 디스크 캐시 플러그인은, 서버리스 컨테이너 환경에서는 오히려 문제를 일으킵니다.

  1. 인메모리 파일시스템(tmpfs) 잠식: Cloud Run 컨테이너의 로컬 디스크(/var/www/html/wp-content/cache)는 영구 하드디스크가 아니라 컨테이너의 RAM을 나눠 쓰는 메모리 공간입니다. 즉, 디스크 캐시 파일이 쌓일수록 정작 PHP가 써야 할 가용 RAM이 줄어들어 OOM 에러를 유발합니다.
  2. 컨테이너 간 캐시 불일치: 트래픽이 늘어 컨테이너가 2개로 확장되면 각 컨테이너의 로컬 캐시가 서로 달라져 일관성이 깨지고, 컨테이너가 0개로 축소(Scale-to-zero)되면 만들어 둔 캐시가 모두 증발합니다.

💡 제가 선택한 대안: ‘엣지 캐싱 위임’과 ‘코어 구텐베르크 블록’

  • 캐싱은 Cloudflare Edge로 일원화: 컨테이너 안에서 로컬 캐시를 만드는 대신 앞단의 Cloudflare Edge Cache에 정적 캐싱을 전담시켰습니다.
  • 경량 블록 테마 채택: 무거운 외부 빌더 대신 워드프레스 코어 블록 에디터(Gutenberg)와 순수 인라인 CSS로 동작하는 가벼운 구성을 선택했습니다.

2. Dockerfile Pre-baking 전략: 컨테이너가 재시작되어도 초기화되지 않는 구조

🛠️ 왜 테마와 플러그인을 도커 이미지에 구워 넣어야(Pre-bake) 할까?

Cloud Run을 처음 써보시는 분들이 가장 당황하는 순간이 있습니다. 워드프레스 관리자 화면(플러그인 > 새로 추가)에서 플러그인과 테마를 열심히 설치해 두었는데, 몇 시간 뒤 다시 접속해 보니 설치했던 플러그인 파일들이 흔적도 없이 사라져 있는 현상입니다.

앞서 살펴보았듯 컨테이너의 로컬 파일시스템은 휘발성이기 때문에, 런타임에 웹 화면에서 다운로드한 파일은 컨테이너가 내려가면 함께 사라집니다. 반면 DB에는 “해당 플러그인이 활성화되어 있다”는 기록만 남아서 에러가 발생하게 됩니다.

“코드가 곧 인프라이며, 도커 이미지 자체가 단 하나의 진실(Single Source of Truth)이어야 한다.”

그래서 저는 필요한 테마와 플러그인을 웹에서 수동으로 설치하는 대신, Dockerfile 빌드 단계에서 공식 저장소로부터 다운로드해 이미지 레이어에 영구적으로 포함시키는 Pre-baking 방식을 택했습니다.

📦 제가 엄선한 경량 패키지 구성

구분 이름 (슬러그) 역할 선택한 기술적 이유
Theme Kadence (kadence) 베이스 레이아웃 불필요한 외부 JS 프레임워크 없이 가벼운 CSS만으로 빠른 로딩 속도를 제공하는 모던 블록 테마
Plugin 1 Kadence Blocks (kadence-blocks) 다단 레이아웃 & 목차 무거운 페이지 빌더 없이도 반응형 레이아웃과 기술 블로그에 필수적인 목차(TOC) 블록 제공
Plugin 2 The SEO Framework (autodescription) 검색엔진 최적화 (SEO) Yoast나 Rank Math에 비해 훨씬 가볍고 불필요한 광고나 과도한 DB 테이블 생성이 없어 e2-micro DB에 최적
Plugin 3 Highlighting Code Block (highlighting-code-block) 코드 문법 강조 기술·퀀트 포스팅에서 가독성 높은 코드 하이라이팅과 원클릭 복사 기능 제공
Plugin 4 WP-Stateless (wp-stateless) GCS 미디어 오프로딩 Keyless ADC 패치와 결합하여 업로드된 이미지를 GCS 버킷에 영구 분리 저장

💻 구현 1: Dockerfile 빌드 시점에 테마·플러그인 내장

Dockerfile에 아래 명령을 추가하여 이미지 빌드 시점에 테마와 플러그인 압축 파일을 받아 컨테이너 내부 경로(/usr/src/wordpress/wp-content/)에 미리 압축을 풀어두었습니다.

# [Dockerfile]
# Pre-install Theme (Kadence) & Recommended Plugins (Kadence Blocks, The SEO Framework, Highlighting Code Block)
RUN curl -fsSL -o /tmp/kadence.zip https://downloads.wordpress.org/theme/kadence.latest-stable.zip 
    && unzip -q /tmp/kadence.zip -d /usr/src/wordpress/wp-content/themes/ 
    && curl -fsSL -o /tmp/kadence-blocks.zip https://downloads.wordpress.org/plugin/kadence-blocks.latest-stable.zip 
    && unzip -q /tmp/kadence-blocks.zip -d /usr/src/wordpress/wp-content/plugins/ 
    && curl -fsSL -o /tmp/autodescription.zip https://downloads.wordpress.org/plugin/autodescription.latest-stable.zip 
    && unzip -q /tmp/autodescription.zip -d /usr/src/wordpress/wp-content/plugins/ 
    && curl -fsSL -o /tmp/hcb.zip https://downloads.wordpress.org/plugin/highlighting-code-block.latest-stable.zip 
    && unzip -q /tmp/hcb.zip -d /usr/src/wordpress/wp-content/plugins/ 
    && rm -f /tmp/kadence.zip /tmp/kadence-blocks.zip /tmp/autodescription.zip /tmp/hcb.zip

💻 구현 2: 컨테이너 기동 시 무인 자동 활성화 (mu-plugins/cloudrun.php)

도커 이미지에 파일을 넣어두었더라도 최초 배포 시 일일이 관리자 화면에 들어가 활성화 버튼을 누르는 것은 번거롭습니다. 그래서 mu-plugins/cloudrun.php에 최초 1회 자동으로 테마를 전환하고 플러그인들을 활성화하는 코드를 넣어 완전 자동화를 구현했습니다.

// [mu-plugins/cloudrun.php]
// Pre-baked 테마(Kadence) 및 필수 플러그인 최초 1회 자동 활성화
add_action('init', function () {
    if (get_option('cloudrun_bundle_a_activated') !== '1') {
        if (function_exists('wp_get_theme') && wp_get_theme('kadence')->exists()) {
            switch_theme('kadence');
        }
        if (!function_exists('activate_plugin')) {
            require_once ABSPATH . 'wp-admin/includes/plugin.php';
        }
        $plugins_to_activate = array(
            'wp-stateless/wp-stateless-media.php',
            'kadence-blocks/kadence-blocks.php',
            'autodescription/autodescription.php',
            'highlighting-code-block/highlighting-code-block.php',
        );
        foreach ($plugins_to_activate as $plugin_file) {
            if (file_exists(WP_PLUGIN_DIR . '/' . $plugin_file) && !is_plugin_active($plugin_file)) {
                activate_plugin($plugin_file, '', false, true);
            }
        }
        delete_transient('kadence_blocks_redirect');
        update_option('cloudrun_bundle_a_activated', '1');
    }
}, 20);

3. 마치며: 가벼운 불변 프론트엔드가 완성되다

이렇게 해서 무거운 상용 페이지 빌더의 메모리 오버헤드를 덜어내고, 컨테이너가 아무리 재기동되거나 0개로 축소(Scale-to-Zero)되더라도 항상 동일한 테마와 플러그인을 유지하는 불변(Immutable) 프론트엔드 계층이 완성되었습니다.

  • 체감 로딩 속도 극대화: 프론트엔드 DOM 트리가 군더더기 없이 깔끔해졌고, 필수 스타일과 스크립트만 로드되므로 브라우저 렌더링 부하가 최소화되었습니다.
  • 운영 안정성 확보: 컨테이너 업데이트나 롤링 배포 시에도 플러그인이 비활성화되거나 파일이 누락되는 불안정성이 원천적으로 제거되었습니다.
  • 리소스 다이어트 성공: 512MB RAM 컨테이너와 1GB DB VM 환경에서도 PHP 메모리 점유율을 안정적인 범위 안에서 철저히 방어해 냈습니다.

이제 모든 랩의 엔진 조립, 미디어 파이프라인, 보안 엣지, 그리고 프론트엔드 최적화까지 모든 퍼즐이 맞춰졌습니다.

다음 편이자 이번 Track 01의 대망의 피날레, [Finish Line: 월 0원 서버리스 워드프레스 완성 아키텍처 대공개]에서는 Warm-up Lap의 순진했던 초기 구상과 180도 달라진 최종 완성 풀스택 아키텍처 다이어그램, 정량 성과 대조표, 그리고 6대 ADR 종합 관제표를 대공개합니다.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *