Oracle ARM + Docker로 WordPress 4사이트 운영하기
📚 1인 인프라 구축기 시리즈 (7편)
Oracle Cloud Free Tier ARM 인스턴스에 Docker로 WordPress 멀티사이트를 올리면서 만난 문제들과 해결 과정. 무료 서버로 블로그 4개 운영하는 실전 인프라 구축기.
블로그를 운영하려면 서버가 필요하다. AWS? 월 $20. Vultr? 월 $6. 그런데 Oracle Cloud Free Tier를 쓰면 ARM 4코어, 24GB RAM, 200GB 디스크를 무료로 쓸 수 있다.
“무료라니 뭔가 함정이 있겠지” 싶었는데, 진짜 함정이 있었다. 인스턴스 생성부터 WordPress 배포까지 삽질의 연속이었다 🔥
🔍 증상: ARM이라 안 되는 것들

Oracle Cloud Free Tier ARM 인스턴스를 생성하고 Docker를 올렸다. docker compose up -d를 실행하니 일부 이미지가 pull이 안 된다.
no matching manifest for linux/arm64/v8 in the manifest list entries
x86 전용 이미지를 ARM에서 당연히 못 돌린다. 공식 이미지 중에서도 ARM을 지원하지 않는 것들이 있었다.
또 다른 문제. Oracle Cloud의 Free Tier ARM 인스턴스는 인기가 많아서 생성 자체가 안 된다. “Out of capacity” 에러가 반복적으로 뜬다. 춘천 리전(ap-chuncheon-1)에서 VM.Standard.A1.Flex를 만들려면 타이밍 싸움이 필요했다.
인스턴스 스펙
Free Tier로 받은 사양이 이 정도다.
| 항목 | 값 |
|---|---|
| Shape | VM.Standard.A1.Flex |
| CPU | 4 OCPU (ARM) |
| RAM | 24GB |
| Disk | 200GB Boot Volume |
| OS | Ubuntu 24.04 (aarch64) |
| 리전 | ap-chuncheon-1 (춘천) |
솔직히 이 스펙이면 블로그 4개는 커녕 10개도 돌릴 수 있다. 문제는 이걸 무사히 세팅하는 과정에 있다.
🧠 원인: 멀티사이트 Docker 구성의 함정들

함정 1 — “Out of capacity” 인스턴스 생성 실패
PAYG(Pay As You Go)로 전환하면 Free Tier 한도 안에서는 과금이 안 되면서 ARM 인스턴스 가용성이 확보된다. PAYG 전환 후 바로 생성에 성공했다.
단, Budget Alert를 반드시 걸어야 한다. 월 $1 기준 3단계 알림(10%/80%/100%)으로 설정해두면 실수로 유료 리소스를 만들어도 즉시 알 수 있다.
함정 2 — ARM에서 Docker 이미지 호환성
WordPress, MariaDB, Redis, Nginx 공식 이미지는 모두 ARM을 지원한다. 하지만 커스텀 이미지를 빌드할 때 base 이미지가 ARM을 지원하는지 반드시 확인해야 한다.
# Dockerfile.wordpress
FROM wordpress:6-php8.3-fpm
RUN curl -o /usr/local/bin/wp \
https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar \
&& chmod +x /usr/local/bin/wp
WP-CLI만 추가한 심플한 Dockerfile이다. wordpress:6-php8.3-fpm이 멀티아키텍처 이미지라 ARM에서도 문제없이 빌드된다.
함정 3 — Nginx ↔ PHP-FPM 경로 불일치
이게 가장 은밀한 함정이었다. 멀티사이트 구성에서 Nginx의 root와 FPM 컨테이너 내부 경로가 다르다.
Nginx (jongmolife.com) → root /var/www/html-life
FPM 컨테이너 내부 → /var/www/html (고정)
Nginx에서 $document_root를 SCRIPT_FILENAME에 넘기면 FPM이 /var/www/html-life/index.php를 찾지만, 실제 파일은 /var/www/html/index.php에 있다. 결과는 빈 화면.
# ❌ 이렇게 하면 두 번째 사이트가 안 된다
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
# ✅ FPM 컨테이너 기준 경로를 별도로 지정
set $fpm_root /var/www/html;
fastcgi_param SCRIPT_FILENAME $fpm_root$fastcgi_script_name;
한 줄 차이로 사이트 전체가 동작하거나 안 하거나다.
함정 4 — Redis DB 번호 분리
Redis 인스턴스 하나로 여러 WordPress를 서빙하면 캐시 키가 충돌한다. DB 번호로 분리해야 한다.
// jongmolee.com — Redis DB 0 (기본값)
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_PASSWORD', '${REDIS_PASSWORD}');
// jongmolife.com — Redis DB 1로 분리
define('WP_REDIS_HOST', 'redis');
define('WP_REDIS_PORT', 6379);
define('WP_REDIS_PASSWORD', '${REDIS_PASSWORD}');
define('WP_REDIS_DATABASE', 1);
이걸 빠트리면 A 사이트 캐시를 B 사이트가 읽어서 이상한 페이지가 뜬다. 은근히 발견이 늦어지는 버그다.
🛠️ 해결: Docker Compose 멀티사이트 아키텍처
최종 구성은 이렇다.
┌──────────────┐
Cloudflare ──→ │ Nginx │ :80/:443
(2+ 도메인) │ (alpine) │
└───┬──────┬───┘
│ │
┌─────────▼┐ ┌─▼──────────┐
│ wordpress │ │ wordpress │
│ (lee.com) │ │ -life │
│ FPM:9000 │ │ FPM:9000 │
└──┬────┬───┘ └──┬────┬────┘
│ │ │ │
┌──▼────▼─────────▼────▼──┐
│ MariaDB (11) │
│ DB: wordpress │
│ DB: wordpress_life │
├─────────────────────────┤
│ Redis (7-alpine) │
│ DB 0: lee DB 1: life │
└─────────────────────────┘
docker-compose.yml 핵심 구조
services:
# MariaDB — 양 사이트 공유
db:
image: mariadb:11
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
MYSQL_DATABASE: wordpress
MYSQL_USER: wordpress
MYSQL_PASSWORD: ${DB_PASSWORD}
volumes:
- ./db-data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 30s
timeout: 10s
retries: 5
# Redis — 오브젝트 캐시 (DB 번호로 사이트 분리)
redis:
image: redis:7-alpine
restart: always
command: redis-server --requirepass ${REDIS_PASSWORD}
# WordPress (jongmolee.com)
wordpress:
build:
context: .
dockerfile: Dockerfile.wordpress
depends_on:
db:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
volumes:
- wordpress-data:/var/www/html
- ./wp-content:/var/www/html/wp-content
# WordPress (jongmolife.com) — DB와 Redis DB 번호만 다름
wordpress-life:
build:
context: .
dockerfile: Dockerfile.wordpress
depends_on:
db:
condition: service_healthy
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_NAME: wordpress_life
WORDPRESS_DB_USER: wordpress
WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
volumes:
- wordpress-life-data:/var/www/html
- ./wp-content-life:/var/www/html/wp-content
# Nginx — SSL 종단 + 리버스 프록시
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx/default.conf:/etc/nginx/conf.d/default.conf
- ./certs:/etc/nginx/certs:ro
사이트를 추가하려면 wordpress-* 서비스를 복제하고, Nginx 설정에 서버 블록을 하나 더 추가하면 된다. 5분이면 사이트 하나가 더 생긴다.
비밀번호 관리 — 셸 특수문자의 저주
.env에 openssl rand -base64 32로 생성한 비밀번호를 넣었다가 사고가 났다. base64에는 =, +, / 같은 문자가 포함되는데, Docker Compose가 이걸 셸 변수로 확장할 때 잘린다.
# ❌ base64 — 특수문자 포함 가능
openssl rand -base64 32
# aB3+cD4/eF5=gH6... → Docker Compose에서 = 이후가 잘릴 수 있음
# ✅ hex — 알파벳 + 숫자만
openssl rand -hex 32
# 4a8f2c... → 특수문자 없음, 안전
교훈: .env 파일의 비밀번호는 hex로만 생성한다. 셸, Docker, PHP 어디서든 파싱 문제가 없다.
🔎 예방: 인프라 체크리스트

Free Tier ARM으로 WordPress를 올릴 때 이 체크리스트를 따라가면 삽질을 대폭 줄일 수 있다.
서버 초기 세팅
# 시스템 업데이트
sudo apt update && sudo apt upgrade -y
# Docker 설치
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
# 스왑 설정 (메모리가 넉넉해도 OOM 방지용)
sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
# 방화벽 (Oracle Cloud iptables)
sudo iptables -I INPUT 6 -m state --state NEW -p tcp --dport 80 -j ACCEPT
sudo iptables -I INPUT 7 -m state --state NEW -p tcp --dport 443 -j ACCEPT
sudo netfilter-persistent save
# SSH 보안 강화
sudo sed -i 's/#PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sed -i 's/#MaxAuthTries.*/MaxAuthTries 3/' /etc/ssh/sshd_config
sudo systemctl restart sshd
WordPress 초기 설정 자동화
# WP-CLI로 설치 마법사를 CLI에서 완료
docker compose exec -T wordpress wp core install \
--url="https://jongmolee.com" \
--title="블로그 제목" \
--admin_user="$(openssl rand -hex 8)" \
--admin_password="$(openssl rand -hex 16)" \
--admin_email="[email protected]" \
--allow-root
# 퍼머링크 설정
docker compose exec -T wordpress wp rewrite structure '/%postname%/' --allow-root
# 불필요한 기본 콘텐츠 삭제
docker compose exec -T wordpress wp post delete 1 2 3 --force --allow-root
중요:
admin_password를 생성한 직후 macOS Keychain이든 비밀번호 관리자든 즉시 저장해야 한다. “나중에 하지” 하면 비밀번호를 아는 사람이 아무도 없는 사태가 벌어진다. 실제로 겪었다 😇
모니터링 3종 세트
# 1. 헬스체크 크론 (5분 간격, 실패 시 Telegram 알림)
*/5 * * * * curl -sf https://jongmolee.com/wp-json/ > /dev/null || \
curl -s "https://api.telegram.org/bot$TOKEN/sendMessage" \
-d "chat_id=$CHAT_ID&text=⚠️ jongmolee.com DOWN"
# 2. 자동 백업 (매일 03:00 KST)
0 18 * * * cd ~/wordpress && ./scripts/backup.sh # UTC 18 = KST 03
# 3. Watchtower (이미지 업데이트 모니터링, 자동 업데이트는 안 함)
docker run -d --name watchtower \
-v /var/run/docker.sock:/var/run/docker.sock \
containrrr/watchtower --monitor-only --schedule "0 0 4 * * *"
✅ 정리
| 증상 | 원인 | 해결 |
|---|---|---|
| ARM에서 Docker 이미지 pull 실패 | x86 전용 이미지 | 멀티아키텍처 공식 이미지 사용 |
| ”Out of capacity” 인스턴스 생성 불가 | Free Tier ARM 가용성 부족 | PAYG 전환 + Budget Alert 설정 |
| 두 번째 사이트 빈 화면 | Nginx root와 FPM 내부 경로 불일치 | $fpm_root 변수로 FPM 경로 지정 |
| 사이트 간 캐시 오염 | Redis DB 번호 미분리 | WP_REDIS_DATABASE 사이트별 설정 |
.env 비밀번호 잘림 | base64 특수문자 셸 파싱 실패 | openssl rand -hex 사용 |
| WP 어드민 로그인 불가 | 비밀번호 미보관 | 생성 즉시 Keychain 저장 필수 |
Oracle Cloud Free Tier ARM은 진짜 무료이면서 스펙도 넉넉하다. 다만 “무료”의 대가는 삽질 시간이다. Docker 기반으로 구성하면 사이트 추가가 5분이고, 서버를 날려도 docker compose up -d 한 방이면 복구된다.
월 서버비 0원으로 블로그 4개를 돌리고 있다. 최고의 가성비 인프라다 💪
📚 1인 인프라 구축기 시리즈 (7편)
- 1. Oracle ARM + Docker로 WordPress 4사이트 운영하기
- 2. Cloudflare Full Strict SSL + Nginx 리버스 프록시 삽질 총정리
- 3. CouchDB + Obsidian LiveSync로 메모 동기화 구축하기
- 4. Umami 셀프호스팅 — Docker 설치부터 AdBlock 우회까지
- 5. GitHub Actions + Cloudflare Pages 자동 배포 — Astro 블로그 CI/CD
- 6. WordPress → Astro 마이그레이션 — 블로그 전환 실전 삽질기
- 7. Claude Max 플랜으로 API 호출하면 429가 뜨는 이유 — 인증 체계 5단계 완전 정리