# 워커 전용 이미지 — solution-backend(:9800)·admin-backend(:9801) 와 **다른 이미지**다. # # ★ 왜 갈랐나 (프리렌더를 워커 실행으로 통합, 2026-09-15) # BUILD·ROLLBACK 잡은 이제 컴파일된 Node 렌더러(solution/site)를 subprocess 로 직접 # 돌린다(services/render_service.py) — 예전에 별도 컨테이너(solution-prerender)가 하던 # 일이다. 그 렌더러가 필요한 건 **워커뿐**이다. Dockerfile(api/admin 겸용)에 Node 를 # 같이 넣으면 API 를 여는 요청만 받는 컨테이너에도 쓰지 않는 Node 런타임과 렌더러 # 번들이 실려 나간다 — 이미지가 커지고 공격 표면도 는다. # # ★ 기동·잡 실행 중에 npm install 을 하지 않는다. site-builder 스테이지가 이미지 빌드 # 시점에 한 번 굽고, 최종 이미지는 컴파일된 산출물만 COPY 한다. # ★ node_modules 는 안 들어간다 — solution/site/vite.config.ts 가 SSR 번들에 # `ssr.noExternal: true` 로 react 등 의존성까지 전부 접어 넣는다(실측: node_modules 를 # 지우고 실행해도 그대로 돈다, dist/prerender/prerender.js 단일 파일 1.8MB). # # ★ 빌드 컨텍스트는 solution-backend 와 같은 **레포 루트**다 — solution/site 와 # solution/backend 를 한 이미지에 담아야 한다. # ── 1단계: 렌더러 컴파일 (Node) ────────────────────────────────────────────── FROM node:24-bookworm AS site-builder WORKDIR /app COPY package.json package-lock.json tsconfig.base.json ./ COPY solution/shared/package.json solution/shared/ COPY solution/frontend/package.json solution/frontend/ COPY solution/site/package.json solution/site/ COPY admin/frontend/package.json admin/frontend/ # ★ npm workspaces 는 루트 package.json 의 workspaces 목록에 있는 자리마다 package.json 을 # 요구한다 — site 만 빌드해도 4개를 전부 COPY 해야 npm ci 가 성공한다(nginx/Dockerfile 과 같다). RUN npm ci COPY solution/shared ./solution/shared COPY solution/site ./solution/site RUN npm run build:client -w @o2o/site \ && npm run build:prerender -w @o2o/site # ── 2단계: Node 런타임만 (바이너리를 뽑아 쓴다) ────────────────────────────── # ★ python:3.12-slim 과 같은 계열(Debian bookworm)의 태그를 써야 한다 — glibc ABI 가 # 어긋나면 바이너리만 복사해서는 못 돈다("cannot execute binary file" 류로 죽는다). FROM node:24-bookworm-slim AS node-runtime # ── 3단계: 최종 워커 이미지 ─────────────────────────────────────────────── FROM python:3.12-slim-bookworm WORKDIR /app COPY solution/backend/requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # ★ yanolja_adapter.py 가 Playwright 로 페이지를 렌더링한다(수집 파이프라인, 워커 전용 경로) — # 패키지(pip)만으로는 브라우저 실행 파일이 없다. --with-deps 가 Chromium 이 필요로 하는 # 시스템 라이브러리(libnss3 등)까지 apt 로 같이 깐다. RUN playwright install --with-deps chromium # Node 바이너리만 옮겨 온다 — npm·corepack 은 쓰지 않는다(빌드는 site-builder 스테이지가 # 이미 끝냈다). /usr/local/bin/node 하나로 `node dist/prerender/prerender.js` 를 돌리는 # 데 충분하다(실측: 아래 COPY 만으로 `node --version` 과 렌더 스모크 테스트가 통과한다). COPY --from=node-runtime /usr/local/bin/node /usr/local/bin/node COPY solution/backend ./solution/backend # ★ SITE_ROOT(solution/site/scripts/prerender.ts)가 자기 파일 위치 기준 상대경로로 # payloads·songs·out 을 찾는다 — dist·public 이 이 자리(/app/solution/site/)에 있어야 # 워커가 컨테이너 안에서 렌더러를 그대로 실행할 수 있다. COPY --from=site-builder /app/solution/site/dist ./solution/site/dist COPY --from=site-builder /app/solution/site/public ./solution/site/public ENV APP_ENV=local ENV PYTHONPATH=/app/solution/backend WORKDIR /app/solution/backend # 워커는 포트가 없다 — 이미지 공통 HEALTHCHECK 를 두지 않는다(compose 가 disable 한다). CMD ["python", "worker_main.py"]