기술스택 근거, 카드마다 코드와 화면 링크로
기술스택 쪽에는 카드가 84개 있다. 카드에는 설명과 상태(지금 사용·경험·배우는 중·학습 예정)만 적혀 있었고 확인할 링크가 없었다. 읽는 사람은 그 말을 확인할 길이 없다. 그래서 카드마다 두 가지를 붙이기로 했다. 그 기술을 실제로 쓴 코드 파일의 주소와, 돌아가는 화면의 주소다. 코드는 깃허브 저장소에, 화면은 깃허브 Pages에 공개했다. Pages는 저장소의 한 브랜치나 /docs 폴더를 공개 주소로 올려 주는 방식을 지원한다.
11월 10일 기준 기술스택 쪽에서 코드·화면 링크가 달린 카드는 37개, 링크가 없는 카드는 47개다. 링크가 없는 카드의 상태는 바꾸지 않았다. Python이나 Playwright처럼 내가 쓰는 도구라도 샘플 저장소 안에서 구현 파일을 확인할 수 없으면 링크를 달지 않았다. 링크를 단 카드 가운데 29개는 상태를 지금 사용으로 올렸다. 경험이던 20개, 배우는 중이던 6개, 학습 예정이던 3개다. 그 결과 지금 사용 카드는 33개에서 62개가 됐다.
샘플은 가상 업체 7곳으로 만들었다. 한 샘플이 여러 기술의 근거를 겸하도록 기능을 섞었다. 아래 표는 저장소와, 그 저장소가 근거가 된 카드다.
| 가상 업체 | 저장소 · 화면 | 근거가 된 카드 |
|---|---|---|
| 여백 스튜디오(클래스 예약) | 코드 · 화면 | HTML5, CSS3, React, TypeScript, 반응형 웹, GitHub Pages, Vite, Jest, ESLint/Prettier, Lighthouse(10개) |
| 소월(꽃 가게 주문) | 코드 · 화면 | Vue.js, Terminal/CLI, Webpack, npm, Cypress(5개) |
| 모래별 여행 데이터 | 코드 · 화면 | JavaScript, REST API, GraphQL, SQL(4개) |
| 느린빛 사진관 | 코드 · 화면 | TensorFlow, Rust, Go, WebAssembly(4개) |
| 결 디지털 전시실 | 코드 · 화면 | Ethereum, Bitcoin, IPFS, MetaMask(4개) |
| 한걸음 클럽(출석 기록) | 코드 · 화면 | React Native, Ionic(2개) |
| 모퉁이 베이크(주문 작업대) | 코드 · 화면 | Node.js, PostgreSQL, MongoDB, Docker, GitHub, Nginx, Redis, Nodemailer(8개) |
모퉁이 베이크 줄의 8개 가운데 SendGrid/Nodemailer 카드는 Nodemailer만 근거가 있다. 그래서 그 카드는 부분 근거로 세었다.
모래별 여행 데이터는 날씨를 REST로, 국가 정보를 GraphQL로 가져온다. 두 방식의 차이가 궁금하면 GraphQL과 REST 비교를 먼저 볼 수 있다.
근거의 무게는 셋으로 나눠 본다. 파일이 있다(구현), 내 PC에서 돌렸다(로컬 실행), 공개 러너에서 돌았다(CI 통과). 러너는 Actions가 작업을 실행하는 가상 컴퓨터다. 카드에 링크를 다는 최소 기준은 첫째 단계였다. 둘째와 셋째는 카드가 아니라 이 글에서 따로 적는다.
예를 들어 Docker는 Dockerfile과 Compose 파일이 있어 카드에 링크를 달았다. 다만 Docker 실행 파일이 없는 작업 PC에서는 Compose와 Nginx 설정을 기동해 보지 못했다. 그래서 Docker와 Nginx 카드의 링크는 설정 파일이 근거이고, 기동 근거는 아니다. 같은 이유로 한걸음 클럽의 Capacitor 설정도 파일까지만 있고 안드로이드·iOS 기기에서는 돌려 보지 않았다.
기술스택 근거가 빈 화면이 될 때 — Pages 하위 경로와 .nojekyll
Pages 주소에는 저장소 이름이 경로로 붙는다. 이 글의 샘플은 junetapa-juncheol.github.io/저장소이름/ 꼴이다. 그래서 빌드 도구에 기준 경로(base)를 알려 줘야 한다. Vite 문서는 https://<USERNAME>.github.io/<REPO>/로 올릴 때 base를 '/<REPO>/'로 정하라고 적는다. 여백 스튜디오의 빌드 설정이 /sample-react-booking/ 기준으로 docs 폴더에 결과를 내는 것도 이 때문이다.
Expo로 만든 한걸음 클럽의 React Native 화면은 /rn/ 아래에 따로 올렸다. Expo 공식 안내는 npx expo export -p web으로 정적 번들을 만들고, 저장소 이름 경로에 올릴 때는 app.json의 experiments.baseUrl에 /저장소이름 꼴 값을 쓰라고 적는다. 배포된 화면의 스크립트 주소가 /sample-mobile-pwa/rn/_expo/static/js/web/ 로 시작하는 것까지 확인했다. Expo 자체가 처음이면 React Native와 Expo 입문을 먼저 볼 수 있다.
문제는 그 _expo 폴더였다. docs 폴더에 .nojekyll 파일이 없을 때 Pages에서 밑줄로 시작하는 _expo 폴더가 빠졌고, 화면은 내용 없이 빈 채로 열렸다.
원인은 Pages의 기본 빌드에 있다. 브랜치의 폴더를 공개하는 방식은 Jekyll(정적 사이트 생성기)을 거친다. Jekyll 말고 다른 빌드를 쓰려면 Actions 워크플로로 올리라고 문서가 권한다. GitHub 문서는 Jekyll이 밑줄(_), 점(.), 샵(#)으로 시작하는 파일과 폴더를 기본으로 빌드하지 않는다고 적는다. Jekyll 자체 문서도 점, 밑줄, 샵, 물결(~)로 시작하는 파일과 폴더는 결과 폴더에 들어가지 않는다고 설명한다.
- 공개 폴더(여기서는 docs) 맨 위에 내용이 빈 .nojekyll 파일을 만든다. 잘 됐으면 깃허브의 docs 폴더 목록에 .nojekyll이 보인다.
- 푸시한 뒤 Actions 탭에서 pages build and deployment 실행이 초록이 될 때까지 기다린다. 잘 됐으면 배포 실행의 상태가 성공으로 바뀐다.
- 밑줄 폴더 아래 파일 주소를 직접 열어 응답 코드를 본다. 잘 됐으면 200이 나온다.
curl -I https://junetapa-juncheol.github.io/sample-mobile-pwa/rn/_expo/static/js/web/index-3f9c3483f6acf6918958448ff5700541.js한걸음 클럽의 docs 폴더에는 .nojekyll이 있고, 위 주소는 200으로 열린다. 점으로 시작하는 파일은 숨김 파일이라 압축을 풀거나 올릴 때 빠지기 쉽다. .nojekyll도, 시험 설정이 든 .github 폴더도 같다. 올린 뒤에는 숨김 파일이 저장소 목록에 보이는지부터 확인한다.
밑줄 폴더가 있는 정적 결과물을 Pages에 올릴 때는 공개 폴더에 빈 .nojekyll 파일을 두고, 폴더 안 파일 주소 하나를 직접 열어 200인지 본다.
인용 — GitHub 블로그This should only be necessary if your site uses files or directories that start with underscores since Jekyll considers these to be special resources and does not copy them to the final site.
우리말 뜻: .nojekyll은 밑줄로 시작하는 파일이나 폴더를 쓰는 사이트에서만 필요하다. Jekyll이 그런 것을 특별한 자원으로 보고 결과 사이트에 복사하지 않기 때문이다.
기술스택 근거를 CI로 이을 때 — Actions의 MongoDB 서비스 컨테이너
주문 API 샘플의 CI는 서비스 컨테이너로 PostgreSQL 17, MongoDB, Redis 7을 띄운 채 린트·시험·빌드를 돌린다. 서비스 컨테이너는 워크플로의 job에 딸려서 데이터베이스나 캐시 같은 서비스를 도커 컨테이너로 붙여 주는 기능이다. 내 PC에서는 MongoDB 8.0.17이 문제없이 돌았기 때문에 CI에서도 MongoDB 8 계열을 쓰려고 했다. MongoDB 자체가 낯설면 MongoDB 안내를, 캐시로 쓴 Redis는 Redis 캐싱 전략을 함께 볼 수 있다.
그런데 Actions에서는 MongoDB 8 서비스 컨테이너가 시작을 거부했다. 원인은 리눅스 커널이다. MongoDB 공식 이슈 SERVER-121912는 리눅스 커널 6.19 업데이트가 MongoDB 8.0 이상에 시작 문제(충돌)를 일으키고, 패키지 관리자나 Docker로 설치한 경우도 같다고 적는다. MongoDB는 이 충돌을 막으려고 커널 6.19 이상에서는 충돌하는 대신 시작 단계에서 종료하도록 점검을 넣었다.
GitHub의 Ubuntu 24.04 러너 이미지 설명에는 커널 버전이 7.0.0-1017-azure로 적혀 있다(이미지 버전 20261101.360.1). 러너 라벨 ubuntu-latest는 이 24.04 이미지를 가리킨다. 커널이 6.19를 넘은 러너에서는 MongoDB 버전에 따라 시작하지 못한다. 이슈의 호환 표는 아래와 같다.
| MongoDB 버전 | 커널 6.19 ~ 7.0.13 | 커널 7.0.14 이상 |
|---|---|---|
| 7.0 전 버전 | 지원 | 지원 |
| 8.0.0 ~ 8.0.20 | 쓰지 말 것: 약 60초 뒤 충돌, 데이터 손상 가능 | 지원 |
| 8.0.21 ~ 8.0.29, 8.3.0 ~ 8.3.8 | 시작 때 종료 | 시작 때 종료 |
| 8.0.35 이상, 8.3.14 이상, 9.0 전 버전 | 시작 때 종료 | 지원 |
표에 없는 중간 버전은 이슈에 따로 적혀 있지 않으므로, 쓰는 버전의 행을 이슈에서 직접 확인해야 한다. 수정판은 8.0.30과 8.3.9에 나왔다고 이슈가 적는다. 다만 Ubuntu 커널은 uname -r이 7.0.0처럼 고정돼 보여 7.0.14 이상 예외가 맞지 않는다는 이슈(SERVER-136848)가 열려 있다. 공식 Docker 이미지 8.0.32, 8.3.11, 9.0.2가 Ubuntu 26.04 커널에서 시작 때 종료한 사례가 적혀 있고, 같은 커널에서 7.0은 정상으로 시작했다고 한다.
나는 서비스 이미지를 mongo:7.0으로 바꿨다. 7.0은 모든 버전이 커널 6.19 이후에서도 지원 대상이다. 로컬은 MongoDB 8.0.17, CI는 7.0이라 두 환경의 버전이 다르다는 점은 남는다. 이 저장소의 마지막 checks 실행은 초록이다.
- 워크플로의 services 아래에서 mongo 이미지 줄을 찾는다. 예를 들면
image: mongo:8같은 줄이다. mongo:7.0으로 바꾸고 이유를 주석으로 남긴다. 아래는 이 저장소의 실제 설정이다.- 푸시한 뒤 실행 기록에서 Initialize containers 단계가 초록인지 본다. 잘 됐으면 이어지는 npm ci와 시험 단계가 시작된다.
mongo:
# MongoDB 8.x refuses to start on Linux kernel 6.19+ (SERVER-121912)
image: mongo:7.0
ports: ['27017:27017']
options: --health-cmd "mongosh --quiet --eval 'db.adminCommand({ping:1})'" --health-interval 3s --health-timeout 3s --health-retries 20시작 거부가 이 문제인지 확인하려면 컨테이너 로그에서 아래 문구를 찾는다. 이슈에 적힌 공식 이미지의 종료 문구다.
MongoDB cannot start: Linux kernel versions 6.19 and newer has a known incompatibility with this version of MongoDB.8.x가 꼭 필요하면 이슈가 안내한 수정 버전(8.0.30 이상, 8.3.9 이상)을 쓰되, Ubuntu 커널에서는 SERVER-136848이 열려 있으므로 CI에서 시작부터 확인한다.
기술스택 근거의 무게 — 로컬과 CI에서 확인한 것
한 샘플은 내 PC에서 끝내 돌리지 못했다. 소월의 Cypress e2e(화면을 처음부터 끝까지 눌러 보는 시험)는 작업 PC의 제한된 Windows 계정에서 앱을 열기도 전에 uv_os_get_passwd returned ENOMEM을 내고 종료했다. 도구를 우회하거나 설정을 건드리지 않았고, 저장소 README에도 로컬 통과를 주장하지 않았다. 대신 Actions의 Ubuntu 러너에서 같은 e2e 단계를 돌렸고, 11월 10일 마지막 실행에서 통과했다.
저장소 7개의 Actions 실행은 11월 10일 한국 시간 오후 2시 11분부터 3시 11분 사이에 76번이었다. 65번이 실패, 11번이 성공이었고 7개 저장소 모두 마지막 실행은 초록이다. 오후 6시 44분에 다시 센 값이다. 실패한 65번의 원인을 전부 이 글에 적지는 않는다. 원인을 확인해 고친 것은 앞의 두 절에 적은 두 건이다.
로컬에서 확인한 값은 따로 읽는다. 여백 스튜디오의 Lighthouse 모바일 점수는 성능·접근성·권장 사항·SEO 네 항목 모두 100점이었다. 11월 10일 10시 18분에 Lighthouse 13.6.0으로 빌드 결과를 내 PC에서 정적으로 제공해 잰 값이다. 공개 주소에서 잰 값이 아니므로 같은 점수를 약속하지 않는다. 자바스크립트는 원본 239,544바이트, gzip 압축 74,085바이트였다. 접근성은 WCAG A·AA 기준 검사를 여백 스튜디오와 소월의 10개 화면 상태에서 돌려 지적이 0건이었다.
시험 결과도 로컬 기록이다. 여백 스튜디오는 Jest 시험 10개가 통과했고, 한걸음 클럽의 Vitest와 모퉁이 베이크의 Jest도 로컬에서 통과했다. 모퉁이 베이크의 로컬 시험은 PGlite(WebAssembly로 돌리는 PostgreSQL 엔진)로 돌렸다. PGlite 문서는 여러 연결을 한 연결 위에 다중화하기 때문에 모든 경우가 보장되지는 않는다고 적는다. 그래서 로컬 통과를 진짜 PostgreSQL 서버의 동시 실행 검증으로 읽지 않는다. CI에서는 PostgreSQL 17 서비스 컨테이너를 붙인 채 시험 단계가 통과했다.
자주 묻는 것
Actions에서 MongoDB 서비스 컨테이너가 시작을 거부하면 무엇부터 보나?
mongo 이미지 태그와 러너의 커널 버전을 대조한다. 러너 이미지 설명에 적힌 커널이 6.19 이상이고 이미지가 MongoDB 8 계열이면 이슈의 호환 표에서 해당 버전의 행을 본다. 컨테이너 로그에 커널 6.19 이상이 이 버전과 호환되지 않는다는 문구가 있으면 같은 문제다. 7.0으로 내리면 표 기준으로 모든 버전이 지원 대상이다.
Pages에서 밑줄 폴더 안 파일만 404일 때는 어떻게 하나?
Jekyll이 밑줄로 시작하는 폴더를 결과에 넣지 않는 것이 원인일 수 있다. 공개 폴더 맨 위에 빈 .nojekyll 파일을 만들고, 배포 실행이 끝난 뒤 파일 주소를 직접 열어 200인지 본다. 파일 이름이 점으로 시작해 올리는 과정에서 빠졌는지도 같이 확인한다.
가상 업체 샘플로 기술스택 근거를 만들어도 되나?
실제 업체나 고객 자료를 쓰지 않으므로 개인정보 부담이 없다. 7개 저장소의 README는 첫 문단에서 가상 업체나 가상 장소를 다룬다고 밝힌다. 실제 서비스 운영 경험과는 다른 근거이므로, 카드 상태를 올릴 때 구현 파일이 있는 기술만 골랐다.
로컬에서 못 돌린 시험을 CI 기록으로 대신 근거 삼아도 되나?
Actions 실행 기록은 공개 저장소에서 누구나 열어 볼 수 있어 확인하기 쉽다. 다만 내 PC 안에서 같은 동작을 했다는 말은 아니다. 소월의 Cypress처럼 로컬에서 막혔다면 그 사실과 CI에서 통과한 사실을 나눠 적는다.
정리 — 근거는 단계를 나눠 적을 때 믿을 만해진다
기술 이름 옆에 붙인 근거는 파일, 로컬 실행, CI 통과의 세 단계로 나눠 적을 때 읽는 사람이 무게를 가늠할 수 있다. 이 글의 샘플은 파일이 있는 기술에만 링크를 달았고, 링크가 없는 47개 카드는 그대로 두었다. 막힌 두 곳은 공식 문서로 원인을 대조한 뒤 풀었다. Pages에서는 공개 폴더에 .nojekyll을 두었고, Actions에서는 MongoDB 서비스 이미지를 7.0으로 낮췄다.
아직 못 한 것도 적어 둔다. Compose와 Nginx 기동, 안드로이드·iOS 기기 실행은 확인하지 못했다. 이 두 가지는 확인하기 전까지 카드의 근거를 설정 파일 수준으로 읽어 달라는 뜻으로 남긴다.
샘플을 공개한 같은 날 사이트에 개발 의뢰 쪽도 열었다. 예약, 주문, 데이터 화면처럼 이 글의 샘플과 비슷한 일을 맡기고 싶다면 개발 의뢰에 문의 방법과 진행 순서를 적어 두었다. 샘플 코드는 기술스택 쪽의 카드에서 바로 열 수 있다.
참고 자료
- GitHub Docs. Configuring a publishing source for your GitHub Pages site — 브랜치와 /docs 폴더를 공개 원본으로 쓰는 방식, Jekyll 말고 다른 빌드는 Actions 권장(2026-11-10 확인). docs.github.com
- GitHub Docs. About GitHub Pages and Jekyll — Jekyll이 기본으로 빌드하지 않는 파일과 폴더(2026-11-10 확인). docs.github.com
- Jekyll. Directory structure — 점·밑줄·샵·물결로 시작하는 파일과 폴더는 결과 폴더에 들어가지 않음(2026-11-10 확인). jekyllrb.com
- GitHub Blog. Bypassing Jekyll on GitHub Pages — .nojekyll 파일로 Jekyll 처리를 건너뛰는 방법과 밑줄 폴더 사유(2026-11-10 확인). github.blog
- Vite. Deploying a Static Site — GitHub Pages 하위 경로 배포에서 base를 저장소 이름으로 정하는 방법(2026-11-10 확인). vite.dev
- Expo. Publishing websites — expo export -p web, GitHub Pages 하위 경로용 baseUrl 설정(2026-11-10 확인). docs.expo.dev
- GitHub Docs. Use Docker service containers — 서비스 컨테이너의 정의와 job 단위 설정(2026-11-10 확인). docs.github.com
- MongoDB JIRA. SERVER-121912 — 커널 6.19 이후 MongoDB 8.0 이상의 시작 문제, Docker 설치 포함, 6.19 이상 커널에서 시작 단계 종료 점검(2026-11-10 확인). jira.mongodb.org
- MongoDB JIRA. SERVER-125742 — 버전별 커널 호환 표, 수정 버전 8.0.30과 8.3.9(2026-11-10 확인). jira.mongodb.org
- MongoDB JIRA. SERVER-136848 — Ubuntu 커널의 uname -r 표기 때문에 7.0.14 이상 예외가 맞지 않는 문제, 열린 상태(2026-11-10 확인). jira.mongodb.org
- GitHub. actions/runner-images Ubuntu 24.04 — 이미지 버전 20261101.360.1의 커널 버전 7.0.0-1017-azure(2026-11-10 확인). github.com
- GitHub. actions/runner-images README — ubuntu-latest 라벨이 가리키는 이미지(2026-11-10 확인). github.com
- Cypress Docs. Run Cypress tests in GitHub Actions — Actions에서 Cypress를 돌리는 단계별 안내(2026-11-10 확인). docs.cypress.io
- PGlite Docs. pglite-socket — 단일 연결 위에 여러 연결을 다중화하는 방식과 한계(2026-11-10 확인). pglite.dev
- junetapa. 기술스택 쪽 — 카드 84개, 코드·화면 링크가 달린 카드 37개, 상태별 카드 수(2026-11-10 확인). junetapa.com
- junetapa. sample-order-api CI 설정 — 서비스 컨테이너 PostgreSQL 17·mongo:7.0·Redis 7 설정(2026-11-10 확인). github.com
- junetapa. sample-react-booking — 빌드 기준 경로, 시험 10개, Lighthouse 실측 기록(2026-11-10 확인). github.com
- junetapa. sample-vue-shop Actions — Cypress e2e 단계 통과 기록(2026-11-10 확인). github.com
- junetapa. sample-mobile-pwa RN 화면 — RN 화면의 _expo 스크립트 경로(2026-11-10 확인). junetapa-juncheol.github.io
- junetapa. sample-order-api Actions — 마지막 checks 실행 성공 기록(2026-11-10 확인). github.com