| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | |||
| 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 12 | 13 | 14 | 15 | 16 | 17 | 18 |
| 19 | 20 | 21 | 22 | 23 | 24 | 25 |
| 26 | 27 | 28 | 29 | 30 | 31 |
- javascript
- 인증
- 크롤링
- 프론트엔드
- next.js
- 소셜로그인
- typescript
- 상태관리
- github actions
- JWT
- Vite
- Kotlin
- 마이그레이션
- php
- 도메인설계
- playwright
- 배포
- React
- 아키텍처
- FastAPI
- 보안
- sqlite
- 온디바이스AI
- 메일자동화
- 백엔드
- python
- REST API
- electron
- Android
- contextBridge
- Today
- Total
HM소프트
Docker 배포 운영과 다국어 전환 구현 — 과제 채점 플랫폼 (3) 본문
Docker Compose로 단일 VM에 배포한 과제 채점 플랫폼의 운영 기록. 빌드·배포 분리, 레지스트리 전체 경로 이미지 참조, 떠도는 이미지 정리로 디스크 고갈 차단, 빌드 시점 플래그로 데모 계정 죽은 코드 제거, AI 프롬프트·브라우저 폼 검증·날짜 로케일까지 잡은 영어 국제화, Nginx SSE 설정을 다룬다.
Docker 배포 운영과 다국어 전환 구현을 정리한다.

3편: 배포와 운영
1편 자동 채점, 2편 집계 버그에 이어 시리즈 마지막이다. 기능 너머의 "어떻게 안정적으로 굴리고, 누가 쓰게 만드느냐" — Docker 배포 운영과 다국어 전환을 다룬다.
컨테이너 이미지 경로 — compose가 pull을 못 하던 문제
배포는 Docker Compose로 했고, 이미지는 Artifact Registry에 올린다. 그런데 docker compose pull이 이미지를 못 가져오는 문제가 있었다. 원인은 compose 파일의 이미지 참조가 레지스트리 전체 경로가 아니었던 것. 짧은 이름으로는 어느 레지스트리인지 알 수 없어 pull이 실패한다.
# 문제: 어느 레지스트리인지 불명 → pull 실패
image: mateflow-api:latest
# 해결: Artifact Registry 전체 경로
image: <region>-docker.pkg.dev/<project>/<repo>/mateflow-api:latest
전체 경로로 바꾸니 docker compose pull이 정상 동작했다.
디스크 풀 재발 방지 — dangling 이미지 정리
운영하며 만난 골치 아픈 문제: 배포를 반복하니 디스크가 가득 찼다. 새 이미지를 빌드/풀 할 때마다 이전 이미지가 태그를 잃고 <none>(dangling)으로 쌓였기 때문이다.
배포 스크립트(deploy.sh)가 배포할 때마다 dangling 이미지를 정리하도록 했다.
# 배포 후 떠도는(dangling) 이미지 정리 — 디스크 풀 재발 방지
docker image prune -f
"배포할수록 디스크가 차오르다 어느 날 배포가 실패한다"는 건 운영에서 흔한 함정이다. 배포 파이프라인에 정리를 넣어 근본적으로 막았다.
데모 계정과 런칭 정리
런칭을 준비하며 운영 디테일을 다듬었다. 데모 계정 패널을 환경에 따라 토글하도록(운영에선 숨김) 정리하고, 런칭용 수정들(인삿말, SNS 푸터 등)을 반영했다. 빌드 설정의 substitution과 체크리스트로 "운영/데모"를 명확히 갈랐다.
다국어 전환 — 한글 → 영어
플랫폼을 영어권에도 내보내기 위해, 흩어져 있던 한글 문자열을 영어로 통일하는 작업을 대대적으로 했다.
- UI 문자열(채점 기능, Badge 등)
- 코드 주석
- API의 채점 피드백·AI 프롬프트·테스트 문구
- 네이티브 폼 검증 메시지(브라우저 기본 메시지를 영어로 강제)
- 날짜/시간 피커를 영어 로케일로
특히 브라우저 네이티브 메시지가 까다로웠다. <input required> 같은 HTML 기본 검증 메시지는 브라우저 언어를 따라 한글로 나온다. 이걸 영어로 강제하려면 커스텀 검증 메시지를 명시해야 했다.
input.addEventListener("invalid", (e) => {
const el = e.target as HTMLInputElement;
if (el.validity.valueMissing) el.setCustomValidity("This field is required");
});
input.addEventListener("input", (e) => (e.target as HTMLInputElement).setCustomValidity(""));
i18n은 "보이는 문자열"만 바꾸면 끝이 아니라, 브라우저가 자동으로 끼워 넣는 문자열(폼 검증, 날짜 피커)까지 챙겨야 완성된다는 걸 배웠다.
정리하며
- 이미지 참조는 레지스트리 전체 경로로 — compose pull이 동작한다.
- 배포 파이프라인에 dangling 정리 — 디스크 풀 재발을 근본 차단.
- i18n은 브라우저 자동 문자열까지 — 폼 검증·날짜 피커 로케일이 마지막 1%.
시리즈를 마치며
세 편에 걸쳐 과제 채점 플랫폼을 정리했다.
- 1편: 정답지 자동 채점과 루브릭 아카이브
- 2편: soft-delete가 만든 집계 버그와 "읽는 쪽의 규율"
- 3편: Docker 배포 운영과 다국어 전환
기능을 만드는 일만큼이나, 데이터를 어떻게 보존·집계하고(2편), 어떻게 배포·국제화하느냐(3편)가 제품의 완성도를 좌우한다는 걸 다시 확인한 프로젝트였다.
'외주 개발일지' 카테고리의 다른 글
| 게임 시스템 설계와 원자적 SQL — 안드로이드 게임 개발 (2) (0) | 2026.06.12 |
|---|---|
| Jetpack Compose + Hilt + Room 안드로이드 아키텍처 (1) (0) | 2026.06.12 |
| 대시보드 집계 버그 잡기 — 소프트 삭제와 '삭제된 과제' (2) (0) | 2026.06.12 |
| 정답지 기반 자동 채점과 루브릭 설계 — 채점 플랫폼 (1) (0) | 2026.06.12 |
| Next.js 15 + React 19 프론트엔드와 지도 UI — 채용 플랫폼 (3) (0) | 2026.06.12 |