Files
arasystem/frontend/Dockerfile
T
ttipo 93147b88b7 feat(docker): 대용량 첨부 — ClamAV 검사기 · 공개 내려받기 호스트 · 받기 동시 연결 상한
- ClamAV 1.4.6 LTS 를 둘로 나눴다: clamd 는 백엔드와만 닿는 internal 네트워크(바깥 길 없음 · 서명 볼륨 읽기 전용),
  freshclam 은 자기 바깥 길에만. 비-root · cap_drop ALL. 운영은 늘 뜨고 개발은 profile clamav.
  clamd.conf 는 한 파일 한도 2047M 까지 끝까지 검사하고 암호 · 한도 초과를 판정으로 알린다(실측표는 mail/README).
  검사 중에 죽으면 남던 임시 파일을 띄울 때 지운다.
- nginx 를 포털 · 공개 내려받기(files.) 두 server 로 가른다(SHARE_PUBLIC_URL 과 CORS_ORIGIN 의 호스트 비교 — 같으면 포털이 함께 연다).
  조각 PUT 17m, 받기는 버퍼링 · 압축 끔 + X-Accel-Buffering 넘김 + 머리글 버퍼 16k,
  limit_conn 은 X-Forwarded-For 마지막 칸으로 IP 마다 공개 10 · 앱 안 30, 전체 공개 400 · 앱 안 200.
  받기 location 은 /api/ 블록 안의 대소문자를 가리지 않는 정규식이다(리뷰 — /FILES/ 같은 변형이 상한을 비켜 갔다).
  접근 로그는 경로 모양과 43자 토큰 모양으로 토큰을 가린다.
- 파일은 named volume <PROJECT_NAME>_files(개발도 — builder 단계가 /app/files 를 node 소유로 만든다), 백업하지 않는다.
- CI infra 잡: nginx 두 꼴 · 받기 경로 변형 시험(가짜 백엔드) · clamconf(-n 없이 기본값까지) · 틀린 SHARE_PUBLIC_URL 거부.
- mail/README: 악성 검사 · 공개 호스트 · NPM 칸(Advanced 다섯 줄) · 메모리 예산 · 문제 해결.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:37:21 +09:00

72 lines
4.7 KiB
Docker

# lock(package-lock.json)을 쓰고 검사하는 npm 은 한 버전으로 고정한다.
# npm 은 버전마다 lock 의 중첩 항목을 다르게 정리해서, 섞어 쓰면 lock 이 매번 바뀐다.
# 바꿀 때는 backend/Dockerfile · .github/workflows/ci.yml 의 NPM_VERSION · docker 스킬 문서를 함께 고친다.
ARG NPM_VERSION=11.6.2
# 빌드는 Node 24 LTS — 패치까지 고정한다 (backend/Dockerfile · ci.yml 의 NODE_VERSION 과 같은 값).
# 갱신 규칙과 함께 고치는 곳은 docker 스킬 문서 "Node 버전" 참조.
ARG NODE_VERSION=24.22.1
FROM node:${NODE_VERSION}-alpine3.24 AS build-stage
ARG NPM_VERSION
WORKDIR /app
# 전역 설치는 root 권한이 필요하므로 USER 전환 전에 수행
RUN npm install -g npm@${NPM_VERSION}
# 개발 환경(docker-compose.dev.yml)은 이 스테이지에서 vite dev 서버를 구동하므로 비-root 로 전환한다.
# /app 소유권을 넘겨야 익명 볼륨(node_modules)이 node 소유로 생성되어 vite 캐시(.vite) 쓰기가 가능하다.
RUN chown node:node /app
USER node
COPY --chown=node:node package*.json ./
RUN npm ci
COPY --chown=node:node . .
ARG BUILD_MODE=production
# 같은 주소 배포 — 브라우저는 화면과 같은 주소의 /api 를 부르고 nginx 가 백엔드로 넘긴다 (compose 도 /api 를 준다)
ARG VITE_API_BASE_URL=/api
ENV VITE_API_BASE_URL=$VITE_API_BASE_URL
RUN npm run build-only -- --mode ${BUILD_MODE}
# 비-root 전용 nginx 이미지 (보안 규칙: 컨테이너 root 실행 금지)
# 1024 미만 포트는 바인드할 수 없으므로 8080 을 사용한다 — nginx.conf 의 listen 과 일치해야 한다.
# 버전까지 고정한다 — nginx.conf 가 add_header_inherit(1.29.3 이상)를 쓰므로, 떠 있는 태그(stable-alpine)의
# 옛 이미지가 남은 호스트에서는 기동에 실패한다. 갱신 규칙·함께 고치는 곳은 docker 스킬 문서 "이미지 버전 고정".
#
# nginx 설정만 담은 단계 — 화면 빌드와 떼어 두어, CI 가 vite 빌드 없이 설정을 검사할 수 있다
# (docker build --target nginx-config frontend → nginx -t). 운영 이미지는 이 단계 위에 화면만 얹는다.
FROM nginxinc/nginx-unprivileged:1.30.6-alpine3.24 AS nginx-config
# nginx.conf 는 템플릿이다 — 컨테이너가 뜰 때 entrypoint 가 ${NGINX_*} 만 채워 /etc/nginx/conf.d/default.conf 를 만든다.
# NGINX_ENVSUBST_FILTER : NGINX_ 로 시작하는 변수만 바꾼다 (nginx 의 $uri·$request_id 등을 건드리지 않게)
# NGINX_ENTRYPOINT_LOCAL_RESOLVERS : /etc/resolv.conf 의 DNS(도커 내장 127.0.0.11)를 NGINX_LOCAL_RESOLVERS 로 내보낸다
ENV NGINX_ENVSUBST_FILTER=^NGINX_ \
NGINX_ENTRYPOINT_LOCAL_RESOLVERS=1
COPY nginx.conf /etc/nginx/templates/default.conf.template
# 템플릿 값 계산 (루트 env 의 첨부·업로드 한도 → nginx 본문 한도, 공개 내려받기 호스트). 실행 권한이 있어야 entrypoint 가 source 한다
COPY --chmod=755 nginx-env.envsh /docker-entrypoint.d/16-nginx-env.envsh
# 두 server(포털 · 공개 내려받기 — 11단계)가 include 하는 location 묶음. 템플릿이 아니다 — envsubst 를 거치지 않고 그대로 읽는다.
# share-open · share-closed 는 server 단계(화면 /s/), share-api-open · share-api-closed 는 /api/ 블록 안(공개 API — 대소문자를 가리지 않는다)
COPY nginx-share-open.conf /etc/nginx/snippets/share-open.conf
COPY nginx-share-closed.conf /etc/nginx/snippets/share-closed.conf
COPY nginx-share-api-open.conf /etc/nginx/snippets/share-api-open.conf
COPY nginx-share-api-closed.conf /etc/nginx/snippets/share-api-closed.conf
COPY nginx-api-errors.conf /etc/nginx/snippets/api-errors.conf
# 워커마다 연결 수 — 이미지 기본 1024 → 4096. 대용량 받기(버퍼링 끔 — 받기 하나가 연결 둘, 공개 400 · 앱 안 200 개까지) · 실시간 알림(브라우저마다 연결 둘을
# 1시간) · 포털 요청이 함께 버티게 (nginx.conf 의 http 단계 "동시 연결 수"). 워커 하나에 몰려도 모자라지 않게 워커마다 잡는다.
# 연결마다 파일 하나(fd)를 쓰므로 compose 가 frontend 의 nofile 을 8192 로 둔다. 이미지의 nginx.conf 꼴이 바뀌어 고치지 못하면 빌드를 멈춘다
RUN sed -i 's/^\([[:space:]]*worker_connections[[:space:]]\{1,\}\)[0-9]\{1,\};/\14096;/' /etc/nginx/nginx.conf \
&& grep -Eq '^[[:space:]]*worker_connections[[:space:]]+4096;' /etc/nginx/nginx.conf
# 운영 이미지 — compose 는 target 없이 빌드하므로 이 단계가 파일의 마지막이어야 한다
FROM nginx-config AS production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
# 이미지 기본 사용자(nginx, uid 101)로 구동 — root 로 되돌리지 말 것
USER nginx
EXPOSE 8080
CMD ["nginx", "-g", "daemon off;"]