바이브코딩 웹서비스 운영 배포 절차

Sunghwa IT · Standard Operating Procedure
SOP · ai.bigsox.com

AI로 만든 서비스를 안전하게 운영에 올리는 표준 절차

대상
성화물산 IT 담당자
적용 범위
임직원·외부 개발자가 AI 도구(Claude, ChatGPT, Cursor 등)로 만든 웹서비스를 사내 서버 ai.bigsox.com에 정식 운영(prod) 배포하는 모든 경우
목적
프로토타입 단계에서는 괜찮았던 설정(인증 없음, 키 공유, 평문 비밀값 등)이 그대로 운영에 올라가는 것을 막고, 접수 → 점검 → 배포 → 운영의 표준 절차를 정한다
바이브코딩 결과물의 전형적 특징: 동작은 하지만 인증이 없거나 꺼져 있고, API 키가 코드/저장소에 하드코딩되어 있으며, 의존성 취약점 점검이 안 되어 있고, 개인정보가 무기한 저장된다. 이 SOP의 점검 항목은 실제 사내 도구에서 발견된 사례를 기반으로 한다.
1 접수 2–4 보안·코드·데이터 점검 5 환경 구성 6 배포 7 Go / No-Go 8 운영
원칙: §7의 P0 항목이 하나라도 미충족이면 실데이터 투입 금지 — 테스트 데이터로만 운영한다.
Step 1

접수 (Intake)

서비스를 넘겨받을 때 개발자(또는 외주사)로부터 다음을 반드시 받아둔다. 없으면 받을 때까지 배포를 시작하지 않는다.

인테이크 질문 — 개발자에게 직접 확인

  1. 로그인이 있는가? 없다면 누구나 URL만 알면 쓸 수 있는가?
  2. API 키는 누구 것인가? 사용량 한도가 걸려 있는가?
  3. 업로드된 파일/개인정보는 어디에, 언제까지 저장되는가?
  4. 코드 어딘가에 비밀번호·키가 직접 적혀 있는가? (커밋 이력 포함)
Step 2

보안 점검 (배포 전 필수)

2.1 비밀값 (Secrets)

2.2 API 키 정책

2.3 인증 (Authentication)

2.4 공개 파일·엔드포인트

Step 3

코드·의존성 점검

Step 4

데이터·개인정보 점검

Step 5

환경 구성 (ai.bigsox.com 표준)

5.1 컨테이너화

5.2 디렉터리·포트 규칙

~/services/<서비스명>/        # 서비스별 디렉터리 (compose.yml, .env)
포트: 내부 포트는 compose 네트워크 안에서만,
      외부 노출은 리버스 프록시로만

5.3 환경변수 (.env)

Step 6

배포

배포 직후 확인

Step 7 · Gate

Go/No-Go 체크리스트 — 실데이터 투입 게이트

아래 P0 전부 충족 전에는 실제 임직원 데이터 사용 금지.
#P0 항목확인
1인증(SSO) 활성화 — 비로그인 접근 차단
2API 키: 회사 명의 + 사용량 한도 설정 + 유출 이력 키 폐기 완료
3임직원 정보 등 내부 데이터가 공개 경로에 없음
4개인정보 보존 기한·삭제 절차 합의
5HTTPS 적용, 직접 포트 노출 없음
6high/critical 의존성 취약점 해소
7백업 동작 확인 (DB + 업로드 볼륨, 복구 시험 1회)

P1 (배포는 가능하나 1개월 내 해소): 모니터링/알림 연결, 운영 문서 보완, 접근 권한 세분화(본인 데이터만 조회 등).

Step 8

운영 (배포 후)

영역기준
백업DB 덤프 + 업로드 볼륨을 일 1회 외부 저장소로. 분기 1회 복구 리허설.
모니터링health check 주기 점검(5분 간격), 디스크·메모리 사용량 알림.
키 관리API 사용량 월 1회 검토 (비정상 사용량 = 유출 신호). 담당자 퇴사/외주 종료 시 관련 키 전부 교체.
업데이트분기 1회 npm audit 재실행. 방치된 서비스는 차단이 원칙 (사용자 없는 서비스는 내림).
롤백직전 이미지 태그를 보존하여 docker compose로 즉시 이전 버전 복귀 가능하게 유지. DB 마이그레이션이 포함된 배포는 롤백 절차를 배포 전에 먼저 적는다.
Handoff

인수인계 시 개발자에게 최종적으로 받아야 하는 것

  1. 운영 .env에 들어갈 모든 변수의 최종 값 (비밀값은 안전한 채널로)
  2. 관리자 권한 이전: 저장소, 도메인, 외부 API 콘솔(키 발급 권한)
  3. 알려진 한계·버그 목록 ("의도적으로 꺼둔 것" 포함 — 예: 인증 비활성, 보존정책 미구현)
  4. 장애 시 연락처와 지원 범위(기간·시간)
Appendix A

재구축 프롬프트 (Rebuild Prompt)

§2~§5의 점검에서 탈락하는 서비스는 고치는 것보다 재구축이 빠른 경우가 많다. 아래 프롬프트를 Claude Code(또는 동급 AI 코딩 도구)에 원본 소스와 함께 주면, 이 SOP가 요구하는 운영 수준을 향해 재구축을 시작할 수 있다. (성화 개인경비 정산 도구를 v1에서 현재 구조로 재구축할 때 실제로 적용한 원칙들을 프롬프트화한 것.)

이 프롬프트는 원샷 솔루션이 아니다. 한 번 실행한다고 운영 가능한 결과물이 나오지 않는다. 재구축은 제대로 된 엔지니어링 작업이다: 여러 차례의 반복, 단계마다의 코드 리뷰, 실데이터에 가까운 테스트가 필요하고, 배포 후에도 버그는 나온다. (실제 사례: 학습 메모리 경로가 금액 한도를 우회해 과다 정산되는 버그는 재구축이 "완성"된 뒤의 테스트에서 발견되어 수정됐다.) 프롬프트는 방향과 기준을 제공할 뿐 — 결과 검증, 우선순위 판단, 최종 책임은 사람(엔지니어) 몫이다. AI가 만든 코드도 §1~§7 전 과정을 그대로 통과해야 한다.

사용법: 원본 소스를 ./legacy/ 폴더에 넣고, 새 빈 저장소에서 Claude Code를 열어 아래를 붙여넣는다. 한국어/영어 중 편한 버전을 사용한다 — 내용은 동일. 복사 버튼으로 전체를 복사할 수 있다.

A.1 한국어 버전

당신은 바이브코딩으로 만들어진 웹서비스를 운영 수준으로 재구축하는 시니어 엔지니어다.
./legacy/ 의 원본 소스를 분석하고, 아래 원칙대로 처음부터 다시 구축하라.

## 0. 원본 분석 — 코드 작성 전에 먼저
- 원본이 실제로 하는 일을 끝에서 끝까지 문서화하라: 입력 → 처리 → 출력.
- 업무 규칙(도메인 로직)을 전부 추출해 목록화하라. AI 프롬프트 문자열 안에 박혀
  있는 규칙(금액 한도, 시간대 분기, 예외 처리)도 빠짐없이 포함하라.
  이 규칙들이 제품의 가치다 — 한 줄도 잃어버리지 마라.
- 원본의 출력물 형식(엑셀 양식, 보고서 등)은 1:1로 보존하라. 사용자의 다운스트림
  시스템(회계, ERP)이 그 형식에 의존한다. 셀 단위까지 원본과 대조하라.

## 1. 아키텍처 — 읽기와 판단을 분리하라
- AI(LLM)는 비결정적 작업(OCR, 추출, 이해)에만 사용하고, 업무 규칙의 적용은
  결정적 코드(rules engine)로 작성하라. 같은 입력 → 항상 같은 결과.
  원본처럼 "읽기+판단"을 한 번의 AI 호출에 합치면 오류가 복합되고 디버깅이 불가능해진다.
- LLM 호출은 tool-schema 로 JSON 출력을 강제하라 (자유 텍스트 파싱 금지).
  재시도/백오프를 포함하라.
- 단계별 모듈로 분리하라: 추출(extract) / 판단(classify) / 출력(export) /
  인증(auth) / 저장(db). 파이프라인 오케스트레이터 하나가 단계를 묶는다.
- 규칙이 못 다루는 unknown 케이스에만 LLM fallback을 허용하되,
  그 결과는 반드시 needs_review 로 표시해 사람 검토로 보내라.

## 2. 보안 — 기본값이 안전해야 한다
- 원본 코드와 git 커밋 이력 전체에서 비밀값(API 키, 비밀번호)을 스캔하고
  발견 즉시 보고하라. 발견된 키는 유출로 간주한다 (교체 대상 목록에 기록).
- 모든 설정은 환경변수로. .env.example 을 제공하고, 코드에 키/URL/포트를
  하드코딩하지 마라. 서버를 옮길 때 코드 수정이 없어야 한다.
- 인증은 Microsoft Entra(Teams) SSO 를 환경변수 게이트로 구현하라:
  env 미설정이면 개발 편의상 열리되 기동 로그에 큰 경고를 출력하고,
  env 설정 시 모든 경로(정적 파일 포함)가 인증 뒤로 들어가야 한다.
- 정적 공개 경로(public/)에 내부 데이터(임직원 명단, 사번 등)를 두지 마라.

## 3. 데이터 — 없어도 돌고, 죽어도 안 무너지게
- DB(Postgres)는 DATABASE_URL 게이트로 선택적으로 구성하라. 미설정 시
  무상태 모드로 완전히 동작해야 한다. 저장은 best-effort:
  DB 장애가 도구 장애가 되어서는 안 된다.
- 마이그레이션은 plain SQL 파일로, 기동 시 자동 적용, 적용 이력을 테이블로 추적.
- 어떤 개인정보를 무엇 때문에 저장하는지 README 에 명시하라
  (보존 기한 결정은 IT 몫이므로 목록만 정확히).

## 4. 검증 — "좋아졌다"를 숫자로 증명하라
- 업무 규칙은 네트워크 없는 단위 테스트로 전부 커버하라.
  특히 돈이 걸린 계산(한도, 캡, 인원 배수)은 케이스별로 빠짐없이.
- 원본의 동작을 그대로 재현하는 legacy 모듈을 따로 만들고, 같은 입력에 대해
  신구 결과를 표로 비교하는 하니스(CLI)를 제공하라. 개선 주장은 이 표로만 한다.

## 5. 운영 준비
- Dockerfile + docker-compose (mem_limit 명시, 영속 데이터는 named volume).
- 헬스체크 엔드포인트를 만들어 키/인증/DB 상태를 노출하라
  (예: GET /api/health → {ok, hasKey, authEnabled, dbConnected}).
- 재실행 가능한 배포 스크립트 (rsync 또는 동등 + compose up --build + 헬스체크).
- 의존성 취약점 점검(npm audit)을 실행하고 high/critical 은 교체하라.

## 진행 방식
- 단계마다 무엇을 발견했고 무엇을 결정했는지 보고하라.
- 원본과 동작이 달라지는 지점은 반드시 명시적으로 알려라. 조용한 동작 변경 금지.
- 의도적으로 미루는 것(인증 off, 보존정책 미구현 등)은 README 에
  "deliberate gaps" 목록으로 남겨 IT 가 Go/No-Go 때 확인할 수 있게 하라.

A.2 English version

You are a senior engineer rebuilding a vibe-coded web service to production grade.
Analyze the original source in ./legacy/ and rebuild it from scratch
following these principles.

## 0. Analyze the original — before writing any code
- Document end-to-end what the original actually does: input → processing → output.
- Extract and list every business rule (domain logic), including rules buried
  inside AI prompt strings (amount limits, time-of-day branches, exception
  handling). These rules are the product's value — do not lose a single line.
- Preserve the original's output formats (Excel templates, reports) 1:1.
  The user's downstream systems (accounting, ERP) depend on them.
  Verify against the original cell by cell.

## 1. Architecture — separate reading from judgment
- Use AI (LLMs) only for non-deterministic work (OCR, extraction, understanding).
  Implement business-rule application as deterministic code (a rules engine).
  Same input → same result, always. Combining "read + judge" in a single AI call,
  as the original does, compounds errors and makes debugging impossible.
- Force JSON output from LLM calls via tool schemas (never parse free text).
  Include retry/backoff.
- Split into stage modules: extract / classify (judge) / export / auth / db,
  tied together by one pipeline orchestrator.
- Allow an LLM fallback only for unknown cases the rules cannot handle,
  and always mark those results needs_review so a human checks them.

## 2. Security — safe by default
- Scan the original code and the full git history for secrets (API keys,
  passwords) and report any finding immediately. Treat a found key as leaked
  (record it on the rotation list).
- All configuration via environment variables. Provide .env.example.
  No hardcoded keys/URLs/ports — moving servers must require zero code changes.
- Implement auth as env-gated Microsoft Entra (Teams) SSO: with env unset the
  app stays open for dev convenience but prints a loud boot warning; with env
  set, every route (static files included) goes behind auth.
- Never put internal data (employee rosters, IDs) in publicly served
  static paths.

## 3. Data — runs without it, survives losing it
- Make the database (Postgres) optional, gated on DATABASE_URL. With it unset,
  the app must work fully in stateless mode. Persistence is best-effort:
  a DB outage must never become a tool outage.
- Migrations as plain SQL files, auto-applied on boot, tracked in a
  history table.
- State in the README exactly what personal data is stored and why
  (retention decisions belong to IT — just keep the list accurate).

## 4. Verification — prove "better" with numbers
- Cover every business rule with network-free unit tests. For money-affecting
  calculations (limits, caps, per-person multipliers), cover every case.
- Build a separate legacy module that reproduces the original's behavior, and
  provide a harness (CLI) that compares old vs new results on the same inputs
  in a table. All claims of improvement come from this table only.

## 5. Operations readiness
- Dockerfile + docker-compose (explicit mem_limit; persistent data on
  named volumes).
- A health check endpoint exposing key/auth/DB status
  (e.g. GET /api/health → {ok, hasKey, authEnabled, dbConnected}).
- A re-runnable deploy script (rsync or equivalent + compose up --build +
  health check).
- Run a dependency vulnerability audit (npm audit) and replace high/critical
  packages.

## Working style
- At every stage, report what you found and what you decided.
- Explicitly flag every point where behavior differs from the original.
  No silent behavior changes.
- Record anything deliberately deferred (auth off, no retention policy, etc.)
  as a "deliberate gaps" list in the README so IT can check it at Go/No-Go.

재구축이 끝나면 결과물을 §1(접수)부터 이 SOP에 태운다 — 프롬프트가 §2~§5를 미리 충족시키도록 설계되어 있지만, 충족했는지의 판정은 사람이 한다. 점검을 건너뛰지 않는다.

Appendix B

현행 아키텍처 레퍼런스 (개인경비 정산 도구)

이 SOP의 기준 구현체인 성화 개인경비 정산 자동화 도구의 현재 아키텍처. 새 서비스를 받을 때 이 구조를 최대한 재사용한다 — 같은 배포 패턴, 같은 인증 모듈, 같은 DB 운영 방식을 쓰면 점검도 운영도 한 가지 방법만 익히면 된다.

B.1 전체 그림 — 한 서버, 여러 서비스

nginx-proxy 하나가 모든 도메인을 받고, 서비스들은 각자 독립된 compose 스택으로 나란히 산다. 개인경비 정산은 그 중 하나의 입주자일 뿐 — 새 서비스가 와도 이 그림에 칸을 하나 더 그리는 것이다.

한 서버에 여러 서비스 — 같은 패턴의 반복 임직원 브라우저 HTTPS · 도메인별 라우팅 한 대의 서버 — ai.bigsox.com (현재: Dalpamon Lightsail) 서비스마다 독립 디렉터리 · compose · .env · internal 네트워크 nginx-proxy + Let's Encrypt — 공유 유일하게 80/443 을 여는 컨테이너 · 도메인별로 각 서비스에 라우팅 · 인증서 자동 개인경비 정산 운영 중 sunghwa-expense.dalpamon.com 앱 컨테이너 :3001 Postgres 16 (전용) 볼륨 se-data · se-db ~/dockers/sunghwa-expense/ .env 는 서버에만 다음 서비스 A service-a.bigsox.com 앱 컨테이너 DB — 전용 또는 공유 볼륨 (named volume) ~/dockers/service-a/ 같은 SOP 로 점검 후 입주 다음 서비스 B … service-b.bigsox.com 앱 컨테이너 DB — 전용 또는 공유 볼륨 (named volume) ~/dockers/service-b/ 서비스끼리는 서로 안 보임 서비스 추가 = 디렉터리 + compose + .env + DNS A 레코드 1줄 — nginx-proxy 와 이웃 서비스는 손대지 않는다

B.2 배포 토폴로지 — 개인경비 정산 한 칸의 내부

임직원 브라우저 HTTPS (443) 서버 — 현재: Dalpamon Lightsail (2GB 공유) → 이관 목표: ai.bigsox.com ~/dockers/sunghwa-expense/ (소스 + compose + .env — .env 는 서버에만 존재) nginx-proxy + Let's Encrypt — 공유 리버스 프록시 서버의 모든 서비스 공용 · DOMAIN 만 주면 라우팅 + SSL 자동 자동 SSL proxy 네트워크 · VIRTUAL_HOST 라우팅 sunghwa-expense — 앱 컨테이너 Express · 내부 :3001 — 외부 포트 직접 노출 없음 mem_limit 512m · 헬스체크 /api/health se-data 영수증 원본 · 학습 메모리 /app/data internal 네트워크 — 외부에서 접근 불가 sunghwa-expense-db postgres:16-alpine · 포트 외부 미노출 mem_limit 256m se-db Postgres 데이터 두 볼륨은 재배포에도 유지 — 서버 이전 시에만 pg_dump / rsync 로 이동 · 배포는 ./deploy.sh (rsync → compose up -d --build → 헬스체크)
앱 / 코드 인프라 · 네트워크 영속 데이터 (볼륨)

B.3 애플리케이션 구조 — 파이프라인

핵심 설계 — 읽기와 판단의 분리 업로드 이미지 · PDF 법인카드 .xls ① 읽기 — AI extract/ 비전·OCR — 글자만 읽는다 vision · pdf · cardStatement 비결정적 (AI) JSON ② 판단 — 규칙엔진 classify/rules.js 식대 한도 · 통신비 · 접대비 · 택시 같은 입력 → 항상 같은 결과 unknown 만 LLM 폴백 → needs_review 결정적 (코드) 화면 검토·수정 사람이 확인 수정 = 학습 입력 export/ — 양식 2종 개인경비 .xlsx + AP .xls vendor_overrides 가맹점 → 계정 학습 수정하면 학습 다음 분류에 반영
src/ 모듈 트리 (펼치기)
src/
  config/accounts.js   계정과목 26종 매핑 + 금액 한도 정책
  extract/             STAGE 1 — 읽기 (AI 는 여기에만)
  classify/            STAGE 2 — 판단 (rules.js 규칙엔진 + memory.js 학습)
  export/excel.js      성화 엑셀 템플릿 채우기 (templates/)
  auth/                Entra(Teams) SSO — env 게이트, Express 미들웨어
  db/                  Postgres — pool / migrate / store (전부 best-effort)
  llm/anthropic.js     Anthropic 클라이언트 (tool-schema JSON 강제 + 재시도)
  pipeline.js          ①→② 오케스트레이터
  server.js            Express 앱 + 전체 라우트
migrations/            plain SQL — 기동 시 자동 적용, _migrations 테이블로 추적
public/                정적 UI (vanilla JS, 빌드 없음)

B.4 데이터베이스 — 스키마 (ERD)

DATABASE_URL 게이트로 선택적 — 미설정이면 무상태 모드, 설정돼 있어도 저장 실패가 도구 장애가 되지 않는다(best-effort).

expense_runs id PK employee_empno · name year · month · team_code total_amount · item_count receipt_uploads id PK stored_path · sha256 extracted JSONB (감사용) processed_by_email expense_items id PK run_id FK receipt_upload_id FK account · settled_amount vendor_overrides vendor_norm PK account count · updated_at app_users email PK empno · name role (admin·accounting·member) 1 N 1 N se-data 볼륨 파일 본문 (DB엔 메타만) _migrations — 적용된 SQL 이력 추적 vendor_overrides: ② 분류 단계에서 참조 · /api/correct 로 학습 app_users: SSO Phase 2 — 본인 정산만 보기

마이그레이션: migrations/*.sql 순서대로 기동 시 자동 적용. 스키마 변경 = SQL 파일 추가 후 재배포. 백업: docker exec sunghwa-expense-db pg_dump -U se sunghwa_expense > backup.sql

B.5 재사용 가이드 — 다음 서비스에 그대로 가져갈 것

새로 만들지 말고 — 복사 · 이식한다 이 저장소 — sunghwa-expense docker-compose.yml + deploy.sh src/auth/ — Entra SSO 미들웨어 src/db/ + migrations/ 패턴 src/llm/anthropic.js — LLM 클라이언트 .env.example 규약 다음 서비스 — 새 접수분 배포 — 이름·도메인만 변경 인증 — 거의 그대로 이식 DB — 같은 게이트·마이그레이션 방식 LLM — 그대로 사용 env — 같은 규약
Postgres 재사용 권장: 소형 서버에서는 서비스마다 DB 컨테이너를 띄우기보다 하나의 Postgres에 서비스별 database(CREATE DATABASE <service>)를 만드는 쪽을 권장 — 메모리 절약, 백업 일원화. 현재 구성은 서비스별 컨테이너(격리 우선)이며 둘 다 유효한 선택.

B.6 ai.bigsox.com 이관 시 바뀌는 것

이관 = 환경값과 데이터의 이동 — 코드 수정 없음 현재 — Dalpamon Lightsail 앱 컨테이너 Postgres 16 볼륨 se-data · se-db 이관 — ai.bigsox.com 앱 컨테이너 (compose 그대로) Postgres (restore) 볼륨 (rsync) pg_dump → pg_restore (DB) se-data 볼륨 rsync (영수증·메모리) .env 재작성 — 성화 키 · Entra 3종 DNS A 레코드 + Entra redirect URI §7 Go/No-Go 재수행 · 인증 ON 으로 출발

코드 수정 없이 환경값만 바꾸면 되도록 설계되어 있다. 이관 체크리스트: