OCI ARM 서버 운영 가이드¶
OCI Always Free ARM 인스턴스의 도메인, SSL, Docker, DB, 백업 운영 전체를 정리한 문서.
1. 서버 사양¶
| 항목 | 스펙 |
|---|---|
| 인스턴스 | OCI Always Free — Ampere A1 |
| CPU | ARM Neoverse-N1, 4 OCPU |
| RAM | 24GB |
| Disk | 45GB (블록 볼륨 200GB까지 무료 확장 가능) |
| OS | Ubuntu 24.04 LTS (aarch64) |
| 리전 | ap-osaka-1 |
| 공인 IP | 161.33.33.221 |
| Tailscale IP | 100.107.94.45 |
Always Free 한도¶
- ARM: 4 OCPU + 24GB RAM (1개 인스턴스에 몰아쓰기 가능)
- AMD: VM.Standard.E2.1.Micro x 2 (1/8 OCPU + 1GB RAM 각각)
- 블록 스토리지: 총 200GB
- 네트워크: 월 10TB 아웃바운드
Idle 인스턴스 회수 정책
Always Free 인스턴스가 7일간 CPU 사용률 20% 미만이면 Oracle이 회수(중지)할 수 있다. Pay As You Go로 업그레이드하면 면제된다.
2. 도메인 및 SSL 인증서¶
2.1 도메인 정보¶
| 도메인 | 발급처 | 갱신 주기 |
|---|---|---|
| lab404.kro.kr | 내도메인.한국 (kro.kr) | 90일 (수동 갱신) |
| found404.kro.kr | 내도메인.한국 (kro.kr) | 90일 (수동 갱신) |
- DNS A 레코드: 두 도메인 모두
161.33.33.221로 설정 - 내도메인.한국은 API를 제공하지 않아 도메인 갱신은 수동으로 해야 함
도메인 만료 주의
90일마다 내도메인.한국 사이트에 접속하여 갱신해야 한다. 만료되면 DNS가 끊긴다.
2.2 SSL 인증서 (Let's Encrypt)¶
| 항목 | 내용 |
|---|---|
| 인증서 이름 | lab404.kro.kr |
| 포함 도메인 | lab404.kro.kr, found404.kro.kr |
| 만료일 | 2026-07-10 |
| 인증서 경로 | /etc/letsencrypt/live/lab404.kro.kr/fullchain.pem |
| 개인키 경로 | /etc/letsencrypt/live/lab404.kro.kr/privkey.pem |
| 갱신 모드 | nginx (standalone 아님) |
| 자동 갱신 | certbot.timer — 하루 2회 자동 체크 |
인증서 갱신은 Certbot이 자동으로 처리한다. nginx 모드이므로 Nginx가 실행 중인 상태에서 갱신된다.
standalone 모드와의 차이
standalone 모드는 Certbot이 직접 80포트를 점유하므로 Nginx와 충돌한다. nginx 모드는 Nginx 플러그인을 통해 갱신하므로 충돌 없이 자동 갱신된다.
수동 갱신 테스트¶
인증서 상태 확인¶
2.3 Nginx 설정¶
Nginx는 호스트에 직접 설치되어 있다 (컨테이너 아님).
- 설정 파일:
/etc/nginx/sites-available/ - 활성화:
/etc/nginx/sites-enabled/(심볼릭 링크)
두 도메인 모두 HTTP → HTTPS 자동 리다이렉트가 설정되어 있다.
Nginx 설정 파일¶
lab404.kro.kr — /etc/nginx/sites-available/lab404.kro.kr
server {
server_name lab404.kro.kr;
root /var/www/lab404.kro.kr;
index index.html;
location / {
try_files $uri $uri/ =404;
}
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/lab404.kro.kr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/lab404.kro.kr/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
}
server {
listen 80;
server_name lab404.kro.kr;
return 301 https://$host$request_uri;
}
found404.kro.kr — 동일 구조, server_name만 다름.
나중에 앱 컨테이너를 띄우면 location / 블록을 proxy_pass로 변경한다:
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
2.4 방화벽 설정¶
OCI Security List (Ingress Rules)¶
| Source CIDR | Protocol | 소스 포트 | 대상 포트 | 설명 |
|---|---|---|---|---|
| 0.0.0.0/0 | TCP | 모두 | 22 | SSH |
| 0.0.0.0/0 | TCP | 모두 | 80 | HTTP |
| 0.0.0.0/0 | TCP | 모두 | 443 | HTTPS |
소스 포트 범위 주의
소스 포트를 특정 포트(80, 443)로 제한하면 외부 접속이 차단된다. 클라이언트의 소스 포트는 랜덤이므로 반드시 "모두(All)"로 설정해야 한다.
OS iptables¶
80, 443 포트가 REJECT 규칙 위에 위치해야 한다.
3. Docker Compose 전략¶
3.1 설계 원칙¶
| 원칙 | 설명 |
|---|---|
| 서로 통신하면 묶고, 아니면 분리 | 같은 웹 스택은 하나의 compose, 독립 서비스는 별도 compose |
| DB는 앱과 분리 | 앱 compose down 해도 DB는 살아있어야 함 |
| 공유 네트워크 | shared라는 외부 Docker 네트워크로 compose 간 통신 |
| 바인드 마운트 + 버전 디렉토리 | 업그레이드 시 버전별 데이터 분리 가능 |
3.2 디렉토리 구조¶
/home/ubuntu/docker/
├── proxy/ ← Nginx 리버스 프록시 (현재 호스트에서 운영)
├── database/
│ ├── compose.yaml ← PostgreSQL 18
│ └── .env ← DB 인증 정보
├── lab404/
│ └── compose.yaml ← lab404 앱 (템플릿)
├── found404/
│ └── compose.yaml ← found404 앱 (템플릿)
└── minecraft/ ← 마인크래프트 서버 (필요 시)
3.3 공유 네트워크¶
3.4 Database compose¶
/home/ubuntu/docker/database/compose.yaml
services:
postgres:
image: postgres:18
container_name: postgres18
restart: unless-stopped
ports:
- "5432:5432"
env_file: .env
volumes:
- /home/ubuntu/postgres/postgres18:/var/lib/postgresql
networks:
- shared
networks:
shared:
external: true
PostgreSQL 18 마운트 경로 변경
PostgreSQL 18부터 마운트 포인트가 /var/lib/postgresql/data에서 /var/lib/postgresql로 변경되었다. 하위에 data/ 디렉토리가 자동 생성된다.
바인드 마운트 경로 (버전별 분리)¶
업그레이드 절차: 1. 현재 버전에서 pg_dumpall 백업 2. 새 버전 컨테이너 시작 (새 디렉토리) 3. dump 파일로 restore 4. compose.yaml에서 이미지 태그 + 볼륨 경로 변경
3.5 운영 명령어¶
# 시작
sudo docker compose -f /home/ubuntu/docker/database/compose.yaml up -d
# 중지
sudo docker compose -f /home/ubuntu/docker/database/compose.yaml down
# 로그 확인
sudo docker logs postgres18
# DB 접속
sudo docker exec -it postgres18 psql -U postgres
4. 데이터베이스¶
4.1 현재 DB 목록¶
| DB명 | 설명 |
|---|---|
| assetmanagement | 회사 자산관리 |
| finance | 재무 (빈 DB) |
| fruitshop | 과일가게 (학습용) |
| maple_tracker | 메이플 트래커 |
| mydb | 테스트 (빈 DB) |
| personal_finance | 개인 금융자산 관리 |
| postgres | 기본 DB |
4.2 이전 경로¶
NAS (PostgreSQL 18, Docker) → OCI ARM (PostgreSQL 18, Docker)
- NAS 접속:
100.83.180.5:5433(Tailscale) - OCI 접속:
localhost:5432또는100.107.94.45:5432(Tailscale)
4.3 dump / restore 명령어¶
# 전체 dump (OCI → 파일)
docker exec postgres18 pg_dumpall -U postgres | gzip > backup.sql.gz
# restore (파일 → OCI)
gunzip -c backup.sql.gz | docker exec -i postgres18 psql -U postgres
pg_dump 버전 일치 필수
클라이언트(pg_dump)와 서버 버전이 다르면 에러가 난다. OCI에 PostgreSQL 18 클라이언트 설치 경로: /usr/lib/postgresql/18/bin/
5. 백업 전략¶
5.1 구조¶
매일 새벽 3시 (cron)
│
▼
pg_dumpall + gzip 압축
│
├─► OCI 로컬 보관 (7일)
│ /home/ubuntu/db-backup/
│
└─► NAS 전송 (Tailscale + SCP, 7일)
/volume1/docker/postgres/backup/
5.2 백업 스크립트¶
/home/ubuntu/scripts/db-backup.sh
#!/bin/bash
# OCI PostgreSQL → NAS 자동 백업
# cron: 0 3 * * * /home/ubuntu/scripts/db-backup.sh
set -euo pipefail
BACKUP_DIR="/home/ubuntu/db-backup"
NAS_HOST="kindsnoopy@home-nas"
NAS_BACKUP_DIR="/volume1/docker/postgres/backup"
KEEP_DAYS=7
DATE=$(date +%Y%m%d_%H%M)
DUMP_FILE="${BACKUP_DIR}/full_${DATE}.sql.gz"
# 1. dump + 압축
docker exec postgres18 pg_dumpall -U postgres | gzip > "${DUMP_FILE}"
# 2. NAS로 전송 (Tailscale 경유, ubuntu 유저의 SSH 키 사용)
sudo -u ubuntu scp -O -i /home/ubuntu/.ssh/id_ed25519 \
"${DUMP_FILE}" "${NAS_HOST}:${NAS_BACKUP_DIR}/" 2>/dev/null \
|| echo "[WARN] NAS 전송 실패 — 로컬 백업만 보관"
# 3. 오래된 로컬 백업 삭제
find "${BACKUP_DIR}" -name "full_*.sql.gz" -mtime +${KEEP_DAYS} -delete
# 4. NAS 오래된 백업 삭제
sudo -u ubuntu ssh -i /home/ubuntu/.ssh/id_ed25519 \
"${NAS_HOST}" "find ${NAS_BACKUP_DIR} -name 'full_*.sql.gz' -mtime +${KEEP_DAYS} -delete" 2>/dev/null \
|| echo "[WARN] NAS 정리 실패"
echo "[OK] backup done: ${DUMP_FILE}"
SCP -O 옵션
Synology NAS는 SFTP가 비활성화되어 있으면 기본 SCP가 실패한다. -O 옵션으로 레거시 SCP 프로토콜을 사용하면 해결된다.
5.3 Cron 설정¶
# root crontab에 등록되어 있음
sudo crontab -l
# 내용
0 3 * * * /home/ubuntu/scripts/db-backup.sh >> /home/ubuntu/db-backup/cron.log 2>&1
- 실행 주체: root (Docker 소켓 접근 필요)
- SSH/SCP는
sudo -u ubuntu로 ubuntu 유저의 키 사용 - 로그:
/home/ubuntu/db-backup/cron.log
5.4 수동 백업 / 복원¶
# 수동 백업 실행
sudo bash /home/ubuntu/scripts/db-backup.sh
# 백업 목록 확인
ls -lh /home/ubuntu/db-backup/
# 복원 (특정 백업 파일)
gunzip -c /home/ubuntu/db-backup/full_20260411_1337.sql.gz \
| sudo docker exec -i postgres18 psql -U postgres
# NAS 백업 확인
ssh kindsnoopy@home-nas "ls -lh /volume1/docker/postgres/backup/"
5.5 백업 모니터링¶
# cron 로그 확인
tail -20 /home/ubuntu/db-backup/cron.log
# 최근 백업 파일 확인
ls -lt /home/ubuntu/db-backup/ | head -5
6. Tailscale 네트워크¶
6.1 기기 목록¶
| 기기 | Tailscale IP | OS |
|---|---|---|
| arm-osaka (OCI) | 100.107.94.45 | Linux |
| desktop (집) | 100.94.119.12 | Windows |
| home-nas | 100.83.180.5 | Linux (Synology) |
| macbook-pro | 100.71.128.33 | macOS |
| 24-a-068 (회사) | 100.111.126.115 | Windows |
6.2 SSH 접속¶
PC마다 SSH 키 등록 필요
각 PC에서 SSH 키를 생성하고 NAS authorized_keys에 등록해야 한다. 키 생성 시 -C "기기명" 코멘트를 붙여 구분한다.
7. 주요 경로 요약¶
| 경로 | 설명 |
|---|---|
/home/ubuntu/docker/ | Docker compose 파일 루트 |
/home/ubuntu/docker/database/ | PostgreSQL compose + .env |
/home/ubuntu/postgres/postgres18/ | DB 데이터 (바인드 마운트) |
/home/ubuntu/db-backup/ | 로컬 백업 파일 (7일 보관) |
/home/ubuntu/scripts/db-backup.sh | 백업 스크립트 |
/etc/nginx/sites-available/ | Nginx 도메인별 설정 |
/etc/letsencrypt/live/lab404.kro.kr/ | SSL 인증서 |
/var/www/lab404.kro.kr/ | lab404 웹 루트 |
/var/www/found404.kro.kr/ | found404 웹 루트 |
/home/ubuntu/docker/wiki404/ | MkDocs compose + Dockerfile |
/home/ubuntu/wiki404/mkdocs.yml | MkDocs 설정 파일 |
8. MkDocs 문서 사이트 (wiki404)¶
8.1 개요¶
| 항목 | 내용 |
|---|---|
| 도메인 | wiki404.kro.kr |
| 소스 | 볼트 99_Publishing/ 폴더 |
| 엔진 | MkDocs Material + 커스텀 이미지 |
| 테마 | shadcn/ui 스타일 CSS |
| 자동 반영 | serve 모드 — 파일 변경 시 자동 리빌드 |
8.2 Docker 구성¶
/home/ubuntu/docker/wiki404/compose.yaml
services:
mkdocs:
build: ./build
container_name: wiki404
restart: unless-stopped
ports:
- "8000:8000"
volumes:
- /home/ubuntu/wiki404/mkdocs.yml:/docs/mkdocs.yml
- /home/ubuntu/obsidian-vault/99_Publishing:/docs/docs
command: ["serve", "--dev-addr=0.0.0.0:8000"]
/home/ubuntu/docker/wiki404/build/Dockerfile
FROM squidfunk/mkdocs-material
RUN pip install --no-cache-dir \
click==8.2.1 \
mkdocs-roamlinks-plugin \
mkdocs-callouts \
mkdocs-glightbox \
mkdocs-minify-plugin \
mkdocs-exclude \
mkdocs-macros-plugin \
pymdown-extensions
click 버전 반드시 8.2.1 고정
click 8.3+에서 MkDocs의 serve 모드 파일 변경 자동 감지가 작동하지 않는다. click==8.2.1로 고정해야 Watching paths for changes 메시지가 나오며 자동 리빌드가 정상 동작한다. 이 이슈는 NAS(Synology) 환경에서도 동일하게 발생했었음.
8.3 운영 명령어¶
# 시작
sudo docker compose -f /home/ubuntu/docker/wiki404/compose.yaml up -d
# 재빌드 (Dockerfile 변경 시)
sudo docker compose -f /home/ubuntu/docker/wiki404/compose.yaml up -d --build
# 로그 확인
sudo docker logs --tail 10 wiki404
# 자동 반영 확인 — 이 메시지가 나와야 정상
# INFO - [HH:MM:SS] Watching paths for changes: 'docs', 'mkdocs.yml'
8.4 제외 폴더¶
mkdocs.yml의 exclude 플러그인으로 제외:
_private/*— 비공개 문서_drafts/*— 미배포 초안
8.5 CSS 커스텀¶
99_Publishing/stylesheets/shadcn.css에 shadcn/ui 스타일 적용:
- 색상 팔레트 (neutral 톤, 라이트/다크)
- 폰트 (Inter + JetBrains Mono)
- 테이블, 코드블록, 콜아웃, 탭, 체크박스
- 검색 바 포커스 링
- TOC 활성 인디케이터
- Back to Top 버튼 (우측 하단, TOC 왼쪽)