콘텐츠로 이동

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 플러그인을 통해 갱신하므로 충돌 없이 자동 갱신된다.

수동 갱신 테스트

sudo certbot renew --dry-run

인증서 상태 확인

sudo certbot certificates

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

sudo iptables -L INPUT -n --line-numbers

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 공유 네트워크

# 생성 (최초 1회)
docker network create shared

# 각 compose.yaml에서 참조
networks:
  shared:
    external: true

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/ 디렉토리가 자동 생성된다.

바인드 마운트 경로 (버전별 분리)

/home/ubuntu/postgres/
├── postgres18/    ← 현재 사용 중
├── postgres19/    ← 나중에 업그레이드 시
└── postgres20/

업그레이드 절차: 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 접속

# OCI → NAS
ssh kindsnoopy@home-nas

# SSH 키: /home/ubuntu/.ssh/id_ed25519 (코멘트: oci-arm-osaka)

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.ymlexclude 플러그인으로 제외:

  • _private/* — 비공개 문서
  • _drafts/* — 미배포 초안

8.5 CSS 커스텀

99_Publishing/stylesheets/shadcn.css에 shadcn/ui 스타일 적용:

  • 색상 팔레트 (neutral 톤, 라이트/다크)
  • 폰트 (Inter + JetBrains Mono)
  • 테이블, 코드블록, 콜아웃, 탭, 체크박스
  • 검색 바 포커스 링
  • TOC 활성 인디케이터
  • Back to Top 버튼 (우측 하단, TOC 왼쪽)