실습 로그 / Proxmox 홈랩

홈랩 3단계 · 포트폴리오 사이트 컨테이너 배포

portfolio VM에 웹 서버를 컨테이너로 띄우고, 포트폴리오 사이트를 배포한다.

진행 과정은 실습 내용을 AI와 함께 정리했고, 배운 점은 그걸 보고 제가 이해한 대로 직접 썼습니다. 배운 점 바로가기 ↓

구축컨테이너트러블슈팅

목표

portfolio VM에 웹 서버를 컨테이너로 띄우고, 포트폴리오 사이트를 배포한다. 먼저 테스트 페이지로 컨테이너 실행과 접속을 확인한 뒤 실제 사이트로 교체한다.

A. Nginx 컨테이너로 테스트 페이지 띄우기

목적

  • 실제 사이트를 만들기 전에 VM에서 웹 서버 컨테이너가 동작하고 다른 기기에서 접속되는지 먼저 확인

1. 테스트 페이지 작성 (portfolio VM)

mkdir -p ~/site && echo '<h1>Hello from my homelab</h1>' > ~/site/index.html

2. Nginx 컨테이너 실행

docker run -d --name site -p 80:80 -v ~/site:/usr/share/nginx/html:ro --restart unless-stopped nginx
옵션 뜻
-d 백그라운드 실행
--name site 컨테이너 이름 지정
-p 80:80 VM의 80번 포트를 컨테이너 80번 포트에 연결
-v ~/site:/usr/share/nginx/html:ro VM의 ~/site 폴더를 Nginx 기본 웹 폴더에 연결. ro는 읽기 전용
--restart unless-stopped VM이 재부팅돼도 자동으로 다시 실행. 직접 멈춘 경우는 제외
nginx Docker Hub의 공식 Nginx 이미지
  • 1번 프로젝트 8단계에서는 Dockerfile로 index.html을 넣은 이미지를 만들었음. 이번에는 폴더를 연결해서, 파일만 고치면 이미지를 다시 만들지 않아도 바로 반영됨

3. 접속 확인

  • 맥북 브라우저에서 http://<Tailscale IP> → 테스트 페이지 표시

2-3A-01 맥북에서 Tailscale 주소로 portfolio VM의 웹 페이지 접속

현재 접근 범위

접속하는 곳 주소 가능 여부
Tailscale에 로그인한 내 기기 <Tailscale IP> 가능
집 와이파이에 연결된 기기 <내부 IP> 가능
외부 일반 사용자 없음 불가
  • 아무나 접속할 수 있게 하는 것은 4단계(도메인·HTTPS·Reverse Proxy)에서 진행

B. Hugo 사이트 구성과 GitHub 업로드

구조

글(.md) → Hugo 변환 → public/ → Nginx 컨테이너 → 브라우저
원본 코드는 GitHub portfolio-site 저장소에 보관

1. Hugo 설치 (portfolio VM)

sudo snap install hugo
hugo version
  • Hugo: 마크다운 글을 디자인·메뉴가 붙은 HTML 사이트로 변환하는 정적 사이트 생성기
  • 선택 이유: 노션 글을 마크다운으로 내보내 거의 그대로 옮길 수 있고, 설치가 간단함
  • 설치 버전: v0.165.0 extended

2. 사이트 생성과 테마

hugo new site ~/portfolio-site && cd ~/portfolio-site
git init
git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
  • 테마는 PaperMod. 테마 코드는 내 저장소에 복사하지 않고 submodule로 원본 저장소를 참조함
  • 설정 파일 hugo.toml: 사이트 제목, 테마, 첫 화면 소개 문구, 메뉴(Projects) 지정

3. 첫 글 작성

  • content/posts/proxmox-homelab.md 생성
  • 파일 맨 위 --- 사이는 글 정보(front matter): title, date, draft, summary
  • draft: false여야 빌드 결과에 포함됨

4. 빌드와 반영

hugo
docker rm -f site
docker run -d --name site -p 80:80 -v ~/portfolio-site/public:/usr/share/nginx/html:ro --restart unless-stopped nginx
  • hugo: public/ 폴더에 완성된 사이트 생성
  • A에서 띄운 컨테이너를 지우고, Nginx가 public/을 보여주도록 다시 실행
  • --minify 옵션은 빌드 오류가 나서 제외함 (트러블슈팅 1)

2-3B-01 Hugo + PaperMod로 만든 포트폴리오 사이트 첫 화면

  • 이후 글 추가 방법: content/posts/에 .md 파일을 넣고 hugo 실행. 컨테이너는 다시 띄울 필요 없음

5. GitHub 업로드

  • 목적: 원본 코드를 VM 밖에 보관. VM이 손상돼도 복구 가능하고, 이후 자동 배포(6단계)의 출발점이 됨
  • GitHub에 빈 저장소 portfolio-site(Public) 생성. README, .gitignore는 만들지 않음

올리지 않을 파일 지정

# .gitignore
public/
resources/_gen/
.hugo_build.lock
  • public/은 hugo로 언제든 다시 만들 수 있는 결과물이라 원본만 올림

VM에서 GitHub 쓰기 권한 받기 (Deploy key)

ssh-keygen -t ed25519 -C "portfolio-vm" -f ~/.ssh/id_ed25519 -N ""
cat ~/.ssh/id_ed25519.pub
  • 출력된 공개키를 저장소 Settings → Deploy keys에 등록, Allow write access 체크
  • Deploy key를 쓴 이유: 이 키는 portfolio-site 저장소 하나에만 접근 가능. VM이 뚫려도 다른 저장소는 안전함
  • Hi <GitHub 계정>/portfolio-site! You've successfully authenticated 확인

업로드

git add .
git commit -m "Hugo 포트폴리오 사이트 초기 구성"
git branch -M main
git remote add origin [email protected]:<GitHub 계정>/portfolio-site.git
git push -u origin main

2-3B-02 GitHub portfolio-site 저장소에 원본 코드 업로드 완료

정리: 열쇠 두 개

열쇠 있는 곳 용도
맥북 키 맥북 맥북 → VM SSH 접속
portfolio-vm 키 portfolio VM VM → GitHub portfolio-site 업로드
  • 저장소 주인 계정으로는 맥북이나 GitHub 웹에서도 수정 가능. Deploy key는 VM에만 준 출입증

C. 사이트 개편과 배포 흐름 정리

목표

테스트용으로 띄운 Hugo 사이트를 실제 포트폴리오 구조로 개편하고, 수정 → GitHub → VM 반영까지의 배포 흐름을 정리한다.

1. 디자인 선택

  • 시안 3개(A 기술 매거진 / B 시스템 지도 / C 엔지니어 작업실) 중 B 시스템 지도 선택
    • 프로젝트끼리의 연결(AWS → 홈랩 → 그 위의 서비스)을 지도로 보여주는 구조
  • PaperMod 테마 원본은 수정하지 않고 layouts/(템플릿 덮어쓰기)와 assets/css/extended/(CSS)로만 변경

2. 콘텐츠 구조

  • 메뉴: 홈 / 프로젝트 / 기록 / 소개
  • 프로젝트: content/projects/<이름>/index.md 파일 하나 = 프로젝트 하나
    • 상단 "30초 요약"(왜 / 내가 한 일 / 직접 확인한 결과) → 면접관이 여기만 읽어도 되게
    • 진행 중이라 상세가 없는 프로젝트는 지도에 점선 칸으로만 표시 (preview: true)
    • 숨길 것은 draft: true → 빌드에서 아예 제외
  • 기록: 노션 실습 로그(AWS 19단계, 홈랩 13단계)를 옮김. IP·계정 ID 등 민감정보는 자리표시로 치환
  • 소개: 역량 → 근거 기록 표
  • 기존 주소(/posts/proxmox-homelab/)는 새 주소로 자동 이동(aliases)

3. 배포 흐름

수정 → (데스크톱) git push → GitHub → (VM) git pull → hugo → Nginx가 public/ 서빙
  • VM에서 한 줄로 반영
ssh <사용자>@<portfolio Tailscale IP> "cd ~/portfolio-site && git pull && hugo"
  • Nginx 컨테이너는 public/을 읽기 전용으로 바로 보여주기 때문에 재시작 필요 없음

4. 데스크톱 접속 환경 추가

  • 데스크톱에 Tailscale 설치, SSH 키(desktop-haewon) 생성
  • 맥북으로 VM에 접속해 데스크톱 공개키를 ~/.ssh/authorized_keys에 추가
echo '<데스크톱 공개키>' >> ~/.ssh/authorized_keys
  • >> : 기존 키 뒤에 덧붙이기 (> 하나면 기존 맥북 키가 지워짐)

5. 접속 세션 점검

  • w : 현재 VM에 로그인한 세션 목록
  • Proxmox 웹 콘솔(tty1)에 4일째 로그인된 세션 발견 → 종료
sudo pkill -KILL -t tty1
  • 그냥 pkill은 종료 요청만 보내서 로그인 셸(bash)이 무시함 → -KILL로 강제 종료
  • 로그아웃일 뿐이라 VM·사이트에는 영향 없음

트러블슈팅

1. hugo --minify 빌드 실패

  • 증상
ERROR error building site: render: failed to process "/posts/proxmox-homelab/index.html": expected comma character or an array or object ending on line 106 and column 40
  • 원인: --minify가 테마가 만든 HTML 안의 코드 조각을 해석하다 실패함. Hugo 최신 버전(v0.165)과 테마 사이의 호환 문제로 추정
  • 해결: --minify 없이 hugo로 빌드. 사이트 동작에는 영향 없음. 용량 최적화는 나중에 다시 확인
  • 참고: WARN deprecated는 앞으로 바뀔 설정 이름에 대한 경고라 빌드에는 영향 없음

2. ssh-keygen 키 저장 실패

  • 증상
Saving key "cat ~/.ssh/id_ed25519.pub" failed: No such file or directory
  • 원인: 키 저장 위치를 묻는 질문 칸에 다음 명령어(cat ...)까지 같이 붙여넣어져 파일 이름으로 인식됨
  • 해결: 질문이 나오지 않게 옵션을 미리 지정
ssh-keygen -t ed25519 -C "portfolio-vm" -f ~/.ssh/id_ed25519 -N ""
  • 재발 방지: 질문을 던지는 명령어는 한 줄씩 실행. 가능하면 옵션으로 답을 미리 넘김

3. VM에서 hugo 빌드 실패 (Hugo 버전 차이)

  • 증상: 사이트 개편 후 VM에서 git pull → hugo 실행 시 빌드 실패
ERROR ... _partials/slug.html:2:4 ... at <return>: wrong number of args for return: want 0 got 2
  • 원인: 작업 환경은 Hugo 0.167, VM은 0.165. with 블록 안에서 return을 쓰는 문법이 0.165에서는 지원되지 않음
  • 해결: 변수에 값을 담아두고 파일 맨 끝에서 한 번만 return 하도록 수정 → VM과 같은 0.165로 빌드 확인 후 배포
  • 재발 방지: 테스트 환경의 Hugo 버전을 VM(운영)과 맞춤. 배포 전 hugo version부터 확인

4. 명령어를 엉뚱한 터미널에서 실행

  • 증상: hugo 인식 안 됨, git fetch .\fix.bundle → not a git repository, del → command not found
  • 원인: 윈도우 PowerShell용 명령을 VM(리눅스) 터미널에, VM용 명령을 PowerShell에 입력함
  • 해결: 프롬프트로 위치 확인 후 다시 실행
    • PS C:\Users\...> → 윈도우
    • <사용자>@portfolio:~$ → VM
  • 재발 방지: 명령 치기 전에 프롬프트 먼저 보기. 원격에서 한 번만 실행할 땐 ssh 사용자@주소 "명령" 형태로 접속 없이 실행

5. 데스크톱에서 VM SSH 접속 timeout

  • 증상: ssh: connect to host ... port 22: Connection timed out
  • 원인: 데스크톱에 Tailscale이 설치되어 있지 않아 100.x 사설망 주소에 도달 불가
  • 해결: 데스크톱에 Tailscale 설치·로그인 → 데스크톱 SSH 키 생성 → 맥북으로 VM에 접속해 authorized_keys에 공개키 추가(>>로 덧붙이기)
  • 참고: 비밀번호 로그인을 막아둬서, 새 기기는 이미 등록된 기기를 통해서만 키를 추가할 수 있음

3단계 용어

  • Hugo : 마크다운 글을 HTML 웹사이트로 바꿔주는 정적 사이트 생성기
  • 정적 사이트 : 미리 만들어둔 HTML 파일을 그대로 보여주는 사이트. DB나 서버 코드가 필요 없음
  • 마크다운 (.md) : # 제목, - 목록처럼 간단한 기호로 서식을 정하는 문서 형식
  • 테마 : 사이트의 디자인과 배치를 정해둔 묶음. 여기서는 PaperMod
  • front matter : 글 파일 맨 위 --- 사이에 적는 글 정보(제목, 날짜, 요약 등)
  • 빌드 : 원본(글, 설정, 테마)을 실제 보여줄 결과물(public/)로 만드는 과정
  • bind mount (-v) : VM의 폴더를 컨테이너 안 폴더에 연결하는 것. VM에서 파일을 바꾸면 컨테이너에도 바로 보임
  • git submodule : 다른 저장소를 내 저장소 안에 복사하지 않고 참조로 연결하는 방식
  • .gitignore : GitHub에 올리지 않을 파일 목록
  • commit / push : commit은 현재 상태를 메모와 함께 저장, push는 그 저장본을 GitHub에 올리는 것
  • remote (origin) : 내 코드를 올릴 원격 저장소 주소. 기본 이름이 origin
  • Deploy key : 저장소 하나에만 접근할 수 있는 SSH 키. 서버에 최소 권한만 주기 위해 사용
  • 공개키 / 개인키 : 공개키는 상대방에 달아두는 자물쇠, 개인키는 내가 가진 열쇠. 개인키는 밖으로 꺼내지 않음

배운 점

직접 작성

A. Nginx 컨테이너로 테스트 페이지 띄우기

  • 실제 사이트 만들기 전 웹 서버 컨테이너가 동작하는 지 확인

    • 웹 서버 : 브라우저가 페이지 요청하면 파일 보내주는 프로그램
      • Nginx : 제일 많이 쓰는 웹 서버, 이미지 이름
        • VM에 없으면 Docker Hub에서 자동으로 받아옴
          • 2단계 hello-world랑 같은 원리
          • vm에는 따로 설치 안 함. 컨테이너 안에만 있음
  • 테스트 페이지 작성

    mkdir -p ~/site && echo '<h1>Hello from homelab</h1>' > ~/site/index.html
    
    • mkdir -p ~/site : home 폴더에 site 폴더 만들기 , -p는 이미 있어도 에러 안 내는 코드
    • > : 출력한 걸 파일에 저장
  • Nginx 컨테이너 실행

    docker run -d --name site -p 80:80 -v ~/site:/usr/share/nginx/html:ro --restart unless-stopped nginx
    
    • -d : 백그라운드 실행
    • --name site : 컨테이너 이름 지정
    • -p 80:80 ⇒ vm 80번 포트를 컨테이너 80 포트에 연결
      • 80번은 웹사이트 기본 창구.
      • VM과 컨테이너 연결시켜주는 선이 -p
    • -v ~~ : VM의 ~~ 폴더를 Nginx 기본 웹 폴더에 연결.
      • -v 왼쪽 : vm 폴더, 오른쪽 : 컨테이너 폴더
      • VM에서 파일만 바꾸면 컨테이너에 바로 반영.
    • ro : 읽기 전용
    • --restart unless-stopped : VM이 재부팅되도 자동으로 Nginx 재실행. 직접 멈춘 경우 제외
  • 접속 확인

    • http://<Tailscale IP>
      • s 없음 : 암호화 안 된 연결 (아직 인증서가 없음)
      • 포트 안 쓴 이유 : 80 기본이라
      • 접근 범위 : 외부 X / Tailscale 등록기기 및 집 WIFI 접속 가능

B. Hugo 사이트 구성과 GitHub 업로드

  • 큰 흐름 : 글 (.md) → Hugo 변환 → public/ → Nginx가 보여줌 → 브라우저

  • Hugo : 마크다운을 디자인, 메뉴가 붙은 HTML 사이트로 만들어주는 프로그램

    • hugo 만 칠 때 돌아감
    • 정적 사이트 생성기 : HTML을 미리 다 만들어둠 (DB, 서버 코드가 필요 없음)
    • 선택 이유 : 노션 글을 마크다운 형식으로 내보내 그대로 옮길 수 있어서 선택
    • AWS 실습과 다른 점
      • EC2에 Nginx 직접 깔아서 같은 역할로 사용
    • Nginx VS Hugo (헷갈렸던 점)
      • Hugo는 만드는 쪽(주방) / Nginx는 보내주는 쪽 (계산대)

    Hugo 사이트 폴더 구조: content/ themes/ hugo.toml, hugo가 만든 결과물 public/

    • public/ : Hugo 가 만든 완성품이 담기는 폴더
      • 그래서 nginx 컨테이너가 ~/portfolio-site/public 을 가리킴
  • Hugo 설치

    sudo snap install hugo
    hugo version
    
    • snap : 우분투 앱 설치 도구
  • 사이트 생성 및 테마

    hugo new site ~/portfolio-site && cd ~/portfolio-site
    git init
    git submodule add --depth=1 https://github.com/adityatelange/hugo-PaperMod.git themes/PaperMod
    
    • hugo new site : 사이트 뼈대 폴더 생성

      • 글은 content/, 설정은 hugo.toml
    • hugo.toml : Hugo 사이트 설정 파일 (핵심 줄)

      baseURL = "https://ihaewon.com/"
      title = "이해원 | Cloud Infra Portfolio"
      theme = "PaperMod"
      
      • baseURL : 사이트 주소. 링크를 만들 때 기준이 됨 (4단계에서 도메인 사고 바꿈)
      • title : 브라우저 탭 제목
      • theme : 쓸 테마. themes/ 폴더 안 이름이랑 같아야 함
    • git init

      • 이 폴더를 git이 관리하게 시작
    • 테마 (paperMod) : 디자인 묶음

      • css 직접 안 짜고 디자인 입힘
    • git submodule : 테마 코드를 복사하지 않고 원본 저장소 주소만 걸어둠

  • 빌드 및 반영

    hugo
    docker rm -f site
    docker run -d --name site -p 80:80 -v ~/portfolio-site/public:/usr/share/nginx/html:ro --restart unless-stopped nginx
    
    • hugo : public/ 에 완성된 사이트 만들기 —> 빌드
    • docker rm -f : A의 테스트 컨테이너 강제 삭제
    • A랑 같은 명령에 연결 폴더만 변경 (~/site —> ~/portfolio-site/public)
    • 이후에 글 고치고 hugo 만 치면 끝
      • nginx가 public/ 을 계속 보고 있어서 컨테이너 재시작 필요 X
  • GitHub 업로드

    • 목적 : 원본을 VM 밖에 보관. VM이 망가져도 추후 복구 가능.

    • .gitignore : 안 올릴 파일 목록

      public/
      resources/_gen/
      .hugo_build.lock
      infra/monitoring/prometheus/targets.local.yml
      
      • public/ 은 hugo로 언제든 다시 만들 수 있어서 안 올렸다
      • resources/_gen/, .hugo_build.lock : hugo가 빌드할 때 만드는 임시 파일
      • targets.local.yml : 실제 IP 파일. 7단계에서 추가함
  • Deploy Key ( VM 키 발급)

    ssh-keygen -t ed25519 -C "portfolio-vm" -f ~/.ssh/id_ed25519 -N ""
    cat ~/.ssh/id_ed25519.pub
    
    • Deploy Key : VM에서 GitHub로 나가는 열쇠

      • 열쇠 : 개인키가 있는 쪽 → 공개키를 걸어둔 쪽 으로만 열림.
      • 사용 이유 : 이 저장소 하나에만 열림. VM이 뚫려도 다른 저장소는 안전함
    • 코드

      • -C : 열쇠 이름표
      • -f : 저장위치 지정
      • -N “” : 비밀번호 없이 → 질문 안 나오게 미리 답함
    • 공개키를 github 저장소 setting에 deploy key 등록.

      • 쓰기 권한 허용
    • cat … pub : 공개키 내용을 화면에 출력 → 이걸 복사해서 GitHub에 등록

    • 확인

      • successfully authenticated 뜨면 성공
  • 업로드

    git add .
    git commit -m "Hugo 포트폴리오 사이트 초기 구성"
    git branch -M main
    git remote add origin [email protected]:GODnjs1007/portfolio-site.git
    git push -u origin main
    
    • add : 올릴 파일 담기
    • commit : 메모와 함께 저장
    • git branch -M main : 기본 브랜치 이름 main으로 설정
    • remote add origin : 올릴 주소 등록
    • push -u : 올리기 , 다음 부터는 git push 만

트러블슈팅

  • hugo --minify 빌드 실패
    • 원인 : Hugo 최신 버전과 테마 호환 문제
    • 해결 : --minify 빼고 hugo로 빌드
  • ssh-keygen 키 저장 실패
    • 원인 : 질문 칸에 다음 명령어까지 같이 붙여넣어짐
    • 해결 : -f, -N 옵션으로 질문 안 나오게 미리 답함
  • VM에서 hugo 빌드 실패
    • 원인 : 작업 환경(0.167)과 VM(0.165) Hugo 버전 차이
    • 해결 : 0.165에서 되는 문법으로 수정. 배포 전 hugo version 확인
  • 명령어를 엉뚱한 터미널에서 실행
    • 원인 : 윈도우 명령을 VM에, VM 명령을 윈도우에 입력
    • 해결 : 치기 전에 프롬프트(PS C: / <사용자>@portfolio) 먼저 보기
  • 데스크톱에서 VM SSH 접속 timeout
    • 원인 : 데스크톱에 Tailscale이 없음
    • 해결 : Tailscale 설치 → 데스크톱 키 만들고 맥북으로 접속해서 authorized_keys에 추가