목표
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>→ 테스트 페이지 표시
현재 접근 범위
| 접속하는 곳 | 주소 | 가능 여부 |
|---|---|---|
| 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)
- 이후 글 추가 방법:
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이 뚫려도 다른 저장소는 안전함
ssh -T [email protected]
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
정리: 열쇠 두 개
| 열쇠 | 있는 곳 | 용도 |
|---|---|---|
| 맥북 키 | 맥북 | 맥북 → 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 1
9단계, 홈랩 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에는 따로 설치 안 함. 컨테이너 안에만 있음
- VM에 없으면 Docker Hub에서 자동으로 받아옴
- Nginx : 제일 많이 쓰는 웹 서버, 이미지 이름
- 웹 서버 : 브라우저가 페이지 요청하면 파일 보내주는 프로그램
-
테스트 페이지 작성
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 접속 가능
- http://
B. Hugo 사이트 구성과 GitHub 업로드
-
큰 흐름 : 글 (.md) → Hugo 변환 → public/ → Nginx가 보여줌 → 브라우저
-
Hugo : 마크다운을 디자인, 메뉴가 붙은 HTML 사이트로 만들어주는 프로그램
- hugo 만 칠 때 돌아감
- 정적 사이트 생성기 : HTML을 미리 다 만들어둠 (DB, 서버 코드가 필요 없음)
- 선택 이유 : 노션 글을 마크다운 형식으로 내보내 그대로 옮길 수 있어서 선택
- AWS 실습과 다른 점
- EC2에 Nginx 직접 깔아서 같은 역할로 사용
- Nginx VS Hugo (헷갈렸던 점)
- Hugo는 만드는 쪽(주방) / Nginx는 보내주는 쪽 (계산대)
- 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에 등록
-
확인
ssh -T [email protected]- 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에 추가



