-
Notifications
You must be signed in to change notification settings - Fork 2
Ec2 deploy 안정화 #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ec2 deploy 안정화 #60
Conversation
포크한 레포에 작동 테스틀 위해 연결 스토리북 형태도 세팅함
구조 수정 및 tailwind prefix에 맞게 mapping
이전 패키지 버전에선 빌드가 안 됐음.
새로고침 막고, mutate처리 및 뒤로가기 정상화
todo 개수 0이 아닌 경우에 동작하도록 수정
api 경로에 따라 debounce하도록 수정 및 시간 제한 수정
기존에 수도 클래스로 적용하면 씹히는 경우가 있어서 props를 직접 사용하는 방식으로 수정
5개 넘어야 보이도록
goalId 동일한 것으로 잡도록 수정 dueDate도 계산해서 추가
게스트 모드라면 쿼리 키 다르게 해 refetch 유도 게스트 모드 기록 체크 및 기록이 있다면 온보딩 생략 중복되는 closeMSW함수 제거
게스트 첫 진입 시 onNext실행하도록 수정
도커 허브는 제 개인으로 해두겠습니답
restart에 대해 always적용
WalkthroughThe production deployment workflow updates the Docker run command to include the --restart always policy for the motimo-fe-production container. All other deployment steps and parameters remain unchanged. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Dev as Developer
participant GH as GitHub Actions
participant EC2 as EC2 Host
participant Docker as Docker Engine
Dev->>GH: Trigger production-deploy workflow
GH->>EC2: SSH to host
GH->>Docker: docker pull <image:tag>
GH->>Docker: docker stop motimo-fe-production
GH->>Docker: docker rm motimo-fe-production
rect rgba(230, 245, 255, 0.6)
note over GH,Docker: Change: add restart policy
GH->>Docker: docker run -d --name motimo-fe-production --restart always ...
end
Docker-->>GH: Container started with restart policy
Estimated code review effort🎯 1 (Trivial) | ⏱️ ~2 minutes Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
✨ Finishing Touches🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
app/onboarding/_components/LoginScreen.tsx (2)
38-41: Stop logging sensitive auth data (URL, access/refresh tokens).Console logs here expose tokens and full URLs; this is a security risk and can leak credentials. Remove or mask.
- console.log("=== OAuth 콜백 처리 시작 ==="); - console.log("현재 URL:", window.location.href); + // console.debug("=== OAuth 콜백 처리 시작 ==="); ... - console.log("URL 파라미터들:"); - console.log("- code:", code); - console.log("- error:", error); - console.log("- token:", token); - console.log("- state:", state); - console.log("- access_token:", accessTokenFromUrl); - console.log("- refresh_token:", refreshTokenFromUrl); + // console.debug("URL 파라미터 요약:", { + // code: !!code, + // error, + // state: !!state, + // access_token_present: !!accessTokenFromUrl, + // refresh_token_present: !!refreshTokenFromUrl, + // }); ... - console.log("✅ Access Token 저장됨:", accessTokenFromUrl); + // console.debug("✅ Access Token 저장됨"); ... - console.log("✅ Refresh Token 저장됨:", refreshTokenFromUrl); + // console.debug("✅ Refresh Token 저장됨"); ... - console.log("현재 auth store 상태:"); - const authState = useAuthStore.getState(); - console.log("- isLoggedIn:", authState.isLoggedIn); - console.log("- oauth_code:", authState.oauthCode); - console.log("- access_token:", authState.accessToken); - console.log("- refresh_token:", authState.refreshToken); + // console.debug("현재 auth store 상태 요약:", { + // isLoggedIn: useAuthStore.getState().isLoggedIn, + // });Also applies to: 53-66, 97-106, 114-120
152-156: Use crypto-strong state and URL-encode redirect_uri.Replace Math.random with crypto and encode redirect_uri to avoid invalid auth requests.
- const state = Math.random().toString(36).substring(2, 15); + const state = Array.from(crypto.getRandomValues(new Uint8Array(16))) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); ... - const redirect_uri = `${process.env.NEXT_PUBLIC_FRONTEND_BASE_URL}/onboarding`; - window.location.href = `${OAUTH_ENDPOINTS.GOOGLE_AUTHORIZE}?redirect_uri=${redirect_uri}&state=${state}`; + const baseUrl = + process.env.NEXT_PUBLIC_FRONTEND_BASE_URL || window.location.origin; + const redirect_uri = `${baseUrl}/onboarding`; + window.location.href = + `${OAUTH_ENDPOINTS.GOOGLE_AUTHORIZE}` + + `?redirect_uri=${encodeURIComponent(redirect_uri)}&state=${state}`;- const state = Math.random().toString(36).substring(2, 15); + const state = Array.from(crypto.getRandomValues(new Uint8Array(16))) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); ... - const redirect_uri = `${process.env.NEXT_PUBLIC_FRONTEND_BASE_URL}/onboarding`; - window.location.href = `${OAUTH_ENDPOINTS.KAKAO_AUTHORIZE}?redirect_uri=${redirect_uri}&state=${state}`; + const baseUrl = + process.env.NEXT_PUBLIC_FRONTEND_BASE_URL || window.location.origin; + const redirect_uri = `${baseUrl}/onboarding`; + window.location.href = + `${OAUTH_ENDPOINTS.KAKAO_AUTHORIZE}` + + `?redirect_uri=${encodeURIComponent(redirect_uri)}&state=${state}`;Also applies to: 168-172
api/service.ts (1)
37-71: Debouncer has race conditions, broken timers in Node, and no awaited request.
- Single shared rejectTimer causes cross-request interference.
- Timer typed as number fails on Node (setTimeout returns object).
- Missing await makes try/catch ineffective.
- Key uses raw query object; collisions likely.
-const debounceer = <T, E>(apiRequest: typeof httpClient.request<T, E>) => { +const debounceer = <T, E>(apiRequest: typeof httpClient.request<T, E>) => { const timeLimit = 300; - const timerDictionary: { [apiFullUrl: string]: number } = {}; - let rejectTimer: (reason?: any) => void; + const timers: Record<string, ReturnType<typeof setTimeout>> = {}; + const rejects: Record<string, (reason?: any) => void> = {}; return ( requestParams: Parameters<typeof httpClient.request<T, E>>[0], ): ReturnType<typeof httpClient.request<T>> => { - const apiFullUrl = `${requestParams.path}?${requestParams.query}`; - const timer = timerDictionary[apiFullUrl]; + const key = [ + requestParams.method ?? "GET", + requestParams.path, + JSON.stringify(requestParams.query ?? {}), + JSON.stringify(requestParams.body ?? {}), + ].join(" "); + const timer = timers[key]; if (timer) { - clearTimeout(timer); - rejectTimer("debouncing"); + clearTimeout(timer); + rejects[key]?.("debouncing"); } - const apiRes: Promise<T> = new Promise((resolve, reject) => { - rejectTimer = reject; - timerDictionary[apiFullUrl] = Number( - // timer = Number( - setTimeout(async () => { - try { - const res = apiRequest(requestParams); - resolve(res); - } catch (error) { - console.error(error); - showToast(`API ERROR`, new Date()); - } - }, timeLimit), - ); - }); + const apiRes: Promise<T> = new Promise((resolve, reject) => { + rejects[key] = reject; + timers[key] = setTimeout(async () => { + try { + const res = await apiRequest(requestParams); + resolve(res as T); + } catch (error) { + reject(error); + showToast(`API ERROR`, new Date()); + } finally { + delete timers[key]; + delete rejects[key]; + } + }, timeLimit); + }); // 토큰 재발급 처리 - tokenHandler(apiRes); + tokenHandler(apiRes); return apiRes; }; };
🧹 Nitpick comments (5)
.dockerignore (1)
1-4: Harden the Docker build context by ignoring env files and common junk.Add .env* and typical noise to avoid leaking secrets and shrinking the context.
.next .git node_modules .gitignore +*.env +.env* +npm-debug.log* +yarn-error.log* +.vscode +.DS_Store +coverage +distapp/onboarding/_components/LoginScreen.tsx (1)
176-189: Guard against unsupported indexedDB.databases() and keep UX consistent.Safari and some contexts don’t support indexedDB.databases(). Add a try/catch + fallback.
- const dbs = await indexedDB.databases(); - const hasGuestDB = dbs.find((db) => db.name === DB_NAME); + let hasGuestDB = false; + try { + const dbs = await (indexedDB as any).databases?.(); + hasGuestDB = Array.isArray(dbs) && !!dbs.find((db) => db.name === DB_NAME); + } catch { + hasGuestDB = false; + }api/service.ts (2)
74-101: 401 handling: emit a typed error (no refresh token) and consider retrying once after reissue.Right now we refresh tokens but never retry the original request, and callers get a rejected promise with a generic Error. Define a specific error type and optionally expose a retry path.
const tokenHandler = async <T, E>( apiRes: ReturnType<typeof httpClient.request<T, E>>, ) => { return apiRes.catch(async (e) => { if (e.status === 401) { const token = useAuthStore.getState().refreshToken; if (!token) { - // api.authController.logout(); - // window.location.href = "/"; - throw new Error("no refresh token"); + class NoRefreshTokenError extends Error { + constructor() { + super("NO_REFRESH_TOKEN"); + this.name = "NoRefreshTokenError"; + } + } + throw new NoRefreshTokenError(); } const tokenRes = await api.authController.reissue({ refreshToken: token, }); if (!tokenRes?.accessToken || !tokenRes?.refreshToken) { throw new Error("token reissue error"); } useAuthStore.setState((states) => ({ ...states, accessToken: tokenRes.accessToken, refreshToken: tokenRes.refreshToken, })); + // Optional: consider retrying the original request here + // return httpClient.request<T, E>(/* original params */); } }); };
7-24: Base URL relies on NEXT_PUBLIC_API_URL baked at build.If this runs in the browser, missing build-time NEXT_PUBLIC_API_URL will result in empty baseUrl. Ensure the Docker build passes this via --build-arg (and the code tolerates empty by falling back to window.location.origin if appropriate).
.github/workflows/production-deploy.yml (1)
5-12: Add manual trigger and prevent overlapping deploys.Useful for hotfixes and to cancel in-flight runs.
on: push: branches: - main + workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest + concurrency: + group: production-deploy + cancel-in-progress: true
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (5)
.dockerignore(1 hunks).github/workflows/production-deploy.yml(1 hunks)Dockerfile(1 hunks)api/service.ts(1 hunks)app/onboarding/_components/LoginScreen.tsx(1 hunks)
🧰 Additional context used
🪛 actionlint (1.7.7)
.github/workflows/production-deploy.yml
16-16: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
20-20: the runner of "docker/login-action@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: chromatic-test
|
|
||
| steps: | ||
| - name: Checkout source code | ||
| uses: actions/checkout@v3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Upgrade checkout action to v4.
actionlint flagged v3 as outdated. Use v4.
- uses: actions/checkout@v3
+ uses: actions/checkout@v4🧰 Tools
🪛 actionlint (1.7.7)
16-16: the runner of "actions/checkout@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🤖 Prompt for AI Agents
In .github/workflows/production-deploy.yml around line 16 the workflow uses
actions/checkout@v3 which is flagged as outdated; update the action reference to
actions/checkout@v4 by changing the version tag to v4 so the workflow uses the
current major release of the checkout action and then run/verify the workflow to
ensure compatibility (no other code changes required unless the newer action
surfaces new inputs or behavior).
| uses: docker/login-action@v2 | ||
| with: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Upgrade docker/login-action to v3.
Use the maintained major version.
- uses: docker/login-action@v2
+ uses: docker/login-action@v3📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uses: docker/login-action@v2 | |
| with: | |
| uses: docker/login-action@v3 | |
| with: |
🧰 Tools
🪛 actionlint (1.7.7)
20-20: the runner of "docker/login-action@v2" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🤖 Prompt for AI Agents
In .github/workflows/production-deploy.yml around lines 20 to 21, the workflow
is using docker/login-action@v2; update the action to the maintained major
release by changing the reference to docker/login-action@v3 and ensure the
existing "with:" inputs (username, password or token, registry) remain
compatible with v3; run a quick workflow lint or dry-run to confirm no input
name changes are required.
| - name: Build and push to Docker Hub | ||
| run: | | ||
| docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} . | ||
| docker push ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Build with buildx, enable caching, and pass required build args for NEXT_PUBLIC_ vars.*
Without --build-arg, NEXT_PUBLIC_FRONTEND_BASE_URL (and API URL) will be empty at build time, breaking client code. Also add an immutable latest tag for easy rollback.
- - name: Build and push to Docker Hub
- run: |
- docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} .
- docker push ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }}
+ - name: Build and push to Docker Hub
+ uses: docker/build-push-action@v5
+ with:
+ push: true
+ context: .
+ tags: |
+ ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }}
+ ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:latest
+ build-args: |
+ NEXT_PUBLIC_FRONTEND_BASE_URL=${{ secrets.NEXT_PUBLIC_FRONTEND_BASE_URL }}
+ NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}
+ cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:buildcache
+ cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:buildcache,mode=max📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - name: Build and push to Docker Hub | |
| run: | | |
| docker build -t ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} . | |
| docker push ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | |
| - name: Build and push to Docker Hub | |
| uses: docker/build-push-action@v5 | |
| with: | |
| push: true | |
| context: . | |
| tags: | | |
| ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | |
| ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:latest | |
| build-args: | | |
| NEXT_PUBLIC_FRONTEND_BASE_URL=${{ secrets.NEXT_PUBLIC_FRONTEND_BASE_URL }} | |
| NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }} | |
| cache-from: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:buildcache | |
| cache-to: type=registry,ref=${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:buildcache,mode=max |
| uses: appleboy/ssh-action@master | ||
| with: | ||
| host: ${{ secrets.EC2_HOST }} | ||
| username: ${{ secrets.EC2_USER }} | ||
| key: ${{ secrets.EC2_SSH_KEY }} | ||
| script: | | ||
| # EC2에서 Docker Hub 이미지 pull | ||
| docker pull ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | ||
|
|
||
| # 기존 컨테이너 중지 및 삭제 | ||
| if [ $(docker ps -q -f name=motimo-fe-production) ]; then | ||
| docker stop motimo-fe-production | ||
| docker rm motimo-fe-production | ||
| fi | ||
|
|
||
| # 새 컨테이너 실행 | ||
| docker run -d --name motimo-fe-production -p 3000:3000 --restart always --env-file ./production.env ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Pin the SSH action version and harden the remote script.
- Avoid @master; pin a version.
- Quote command substitutions in test to prevent syntax errors when empty.
- Use set -euo pipefail for safer execution.
- Use absolute path for env-file to avoid cwd issues.
- Ensure idempotent stop/remove.
- uses: appleboy/ssh-action@master
+ uses: appleboy/ssh-action@v1.0.0
with:
host: ${{ secrets.EC2_HOST }}
username: ${{ secrets.EC2_USER }}
key: ${{ secrets.EC2_SSH_KEY }}
script: |
- # EC2에서 Docker Hub 이미지 pull
+ set -euo pipefail
+ # EC2에서 Docker Hub 이미지 pull
docker pull ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }}
- # 기존 컨테이너 중지 및 삭제
- if [ $(docker ps -q -f name=motimo-fe-production) ]; then
- docker stop motimo-fe-production
- docker rm motimo-fe-production
- fi
+ # 기존 컨테이너 중지 및 삭제 (idempotent)
+ docker rm -f motimo-fe-production 2>/dev/null || true
# 새 컨테이너 실행
- docker run -d --name motimo-fe-production -p 3000:3000 --restart always --env-file ./production.env ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }}
+ docker run -d --name motimo-fe-production \
+ -p 3000:3000 \
+ --restart always \
+ --env-file ${{ secrets.PRODUCTION_ENV_PATH }} \
+ ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }}
+
+ # 오래된 이미지 정리(선택)
+ docker image prune -f📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| uses: appleboy/ssh-action@master | |
| with: | |
| host: ${{ secrets.EC2_HOST }} | |
| username: ${{ secrets.EC2_USER }} | |
| key: ${{ secrets.EC2_SSH_KEY }} | |
| script: | | |
| # EC2에서 Docker Hub 이미지 pull | |
| docker pull ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | |
| # 기존 컨테이너 중지 및 삭제 | |
| if [ $(docker ps -q -f name=motimo-fe-production) ]; then | |
| docker stop motimo-fe-production | |
| docker rm motimo-fe-production | |
| fi | |
| # 새 컨테이너 실행 | |
| docker run -d --name motimo-fe-production -p 3000:3000 --restart always --env-file ./production.env ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | |
| uses: appleboy/ssh-action@v1.0.0 | |
| with: | |
| host: ${{ secrets.EC2_HOST }} | |
| username: ${{ secrets.EC2_USER }} | |
| key: ${{ secrets.EC2_SSH_KEY }} | |
| script: | | |
| set -euo pipefail | |
| # EC2에서 Docker Hub 이미지 pull | |
| docker pull ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | |
| # 기존 컨테이너 중지 및 삭제 (idempotent) | |
| docker rm -f motimo-fe-production 2>/dev/null || true | |
| # 새 컨테이너 실행 | |
| docker run -d --name motimo-fe-production \ | |
| -p 3000:3000 \ | |
| --restart always \ | |
| --env-file ${{ secrets.PRODUCTION_ENV_PATH }} \ | |
| ${{ secrets.DOCKERHUB_USERNAME }}/motimo-fe-production:${{ github.sha }} | |
| # 오래된 이미지 정리 (선택) | |
| docker image prune -f |
🤖 Prompt for AI Agents
.github/workflows/production-deploy.yml around lines 33 to 49: the SSH action is
pinned to @master and the remote script is fragile and not robust; update the
action to a specific stable tag (e.g., v0.1.4 or latest semver release) instead
of @master, add a safe shell header (set -euo pipefail) at the top of the remote
script, quote command substitutions when testing container presence (e.g., use
"[ -n "$(docker ps -q -f name=motimo-fe-production)" ]"), make stop/remove
idempotent by first checking for any container IDs (use docker ps -aq -f
name=motimo-fe-production) and then conditionally stopping/removing, and use an
absolute path for the --env-file (or pass env vars via --env-file
$PWD/production.env) to avoid cwd issues.
| # 베이스 이미지로 Node.js 20-alpine 버전을 사용합니다. alpine은 경량화된 버전입니다. | ||
| FROM node:20-alpine | ||
|
|
||
| # 작업 디렉토리를 /app으로 설정합니다. | ||
| WORKDIR /app | ||
|
|
||
| # package.json과 package-lock.json (또는 yarn.lock)을 복사합니다. | ||
| COPY package*.json ./ | ||
|
|
||
| # 의존성을 설치합니다. | ||
| RUN npm install | ||
|
|
||
| # 프로젝트의 모든 파일을 작업 디렉토리로 복사합니다. | ||
| COPY . . | ||
|
|
||
|
|
||
| ############## 빌드 시점 환경 변수 주입하기. | ||
| ARG NEXT_PUBLIC_FRONTEND_BASE_URL | ||
|
|
||
| ENV NEXT_PUBLIC_FRONTEND_BASE_URL=$NEXT_PUBLIC_FRONTEND_BASE_URL | ||
|
|
||
|
|
||
| # Next.js 애플리케이션을 빌드합니다. | ||
| RUN npm run build | ||
|
|
||
| # 3000번 포트를 외부에 노출합니다. | ||
| EXPOSE 3000 | ||
|
|
||
| # 컨테이너가 시작될 때 실행할 명령어를 설정합니다. | ||
| CMD ["npm", "start"] No newline at end of file |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Make the image production-grade: multi-stage, reproducible, smaller, non-root, and pass required build args.
Current image installs dev deps, bakes env incorrectly, and runs as root. Adopt a Next.js production pattern and ensure NEXT_PUBLIC_* are provided at build. Also set NODE_ENV and use npm ci.
-# 베이스 이미지로 Node.js 20-alpine 버전을 사용합니다. alpine은 경량화된 버전입니다.
-FROM node:20-alpine
-
-# 작업 디렉토리를 /app으로 설정합니다.
-WORKDIR /app
-
-# package.json과 package-lock.json (또는 yarn.lock)을 복사합니다.
-COPY package*.json ./
-
-# 의존성을 설치합니다.
-RUN npm install
-
-# 프로젝트의 모든 파일을 작업 디렉토리로 복사합니다.
-COPY . .
-
-############## 빌드 시점 환경 변수 주입하기.
-ARG NEXT_PUBLIC_FRONTEND_BASE_URL
-ENV NEXT_PUBLIC_FRONTEND_BASE_URL=$NEXT_PUBLIC_FRONTEND_BASE_URL
-
-# Next.js 애플리케이션을 빌드합니다.
-RUN npm run build
-
-# 3000번 포트를 외부에 노출합니다.
-EXPOSE 3000
-
-# 컨테이너가 시작될 때 실행할 명령어를 설정합니다.
-CMD ["npm", "start"]
+# syntax=docker/dockerfile:1.7
+# ---- deps (prod+dev) ----
+FROM node:20-alpine AS deps
+WORKDIR /app
+ENV CI=true
+COPY package*.json ./
+RUN npm ci
+
+# ---- builder ----
+FROM node:20-alpine AS builder
+WORKDIR /app
+ENV NODE_ENV=production
+# Build-time PUBLIC envs (must be provided via --build-arg)
+ARG NEXT_PUBLIC_FRONTEND_BASE_URL
+ARG NEXT_PUBLIC_API_URL
+ENV NEXT_PUBLIC_FRONTEND_BASE_URL=${NEXT_PUBLIC_FRONTEND_BASE_URL}
+ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
+COPY --from=deps /app/node_modules ./node_modules
+COPY . .
+# Optional: ensure standalone output for smaller runtime image
+# RUN npx next telemetry disable
+RUN npm run build
+
+# ---- runner ----
+FROM node:20-alpine AS runner
+WORKDIR /app
+ENV NODE_ENV=production
+# Non-root user
+RUN addgroup -S nodejs && adduser -S nextjs -G nodejs
+COPY --from=builder /app/.next ./.next
+COPY --from=builder /app/public ./public
+COPY --from=builder /app/package*.json ./
+COPY --from=deps /app/node_modules ./node_modules
+USER nextjs
+EXPOSE 3000
+CMD ["npm", "start"]Notes:
- These NEXT_PUBLIC_* are compiled at build; ensure the workflow passes --build-arg values. Runtime env-file alone won’t affect client-side code baked at build.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # 베이스 이미지로 Node.js 20-alpine 버전을 사용합니다. alpine은 경량화된 버전입니다. | |
| FROM node:20-alpine | |
| # 작업 디렉토리를 /app으로 설정합니다. | |
| WORKDIR /app | |
| # package.json과 package-lock.json (또는 yarn.lock)을 복사합니다. | |
| COPY package*.json ./ | |
| # 의존성을 설치합니다. | |
| RUN npm install | |
| # 프로젝트의 모든 파일을 작업 디렉토리로 복사합니다. | |
| COPY . . | |
| ############## 빌드 시점 환경 변수 주입하기. | |
| ARG NEXT_PUBLIC_FRONTEND_BASE_URL | |
| ENV NEXT_PUBLIC_FRONTEND_BASE_URL=$NEXT_PUBLIC_FRONTEND_BASE_URL | |
| # Next.js 애플리케이션을 빌드합니다. | |
| RUN npm run build | |
| # 3000번 포트를 외부에 노출합니다. | |
| EXPOSE 3000 | |
| # 컨테이너가 시작될 때 실행할 명령어를 설정합니다. | |
| CMD ["npm", "start"] | |
| # syntax=docker/dockerfile:1.7 | |
| # ---- deps (prod+dev) ---- | |
| FROM node:20-alpine AS deps | |
| WORKDIR /app | |
| ENV CI=true | |
| COPY package*.json ./ | |
| RUN npm ci | |
| # ---- builder ---- | |
| FROM node:20-alpine AS builder | |
| WORKDIR /app | |
| ENV NODE_ENV=production | |
| # Build-time PUBLIC envs (must be provided via --build-arg) | |
| ARG NEXT_PUBLIC_FRONTEND_BASE_URL | |
| ARG NEXT_PUBLIC_API_URL | |
| ENV NEXT_PUBLIC_FRONTEND_BASE_URL=${NEXT_PUBLIC_FRONTEND_BASE_URL} | |
| ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL} | |
| COPY --from=deps /app/node_modules ./node_modules | |
| COPY . . | |
| # Optional: ensure standalone output for smaller runtime image | |
| # RUN npx next telemetry disable | |
| RUN npm run build | |
| # ---- runner ---- | |
| FROM node:20-alpine AS runner | |
| WORKDIR /app | |
| ENV NODE_ENV=production | |
| # Non-root user | |
| RUN addgroup -S nodejs && adduser -S nextjs -G nodejs | |
| COPY --from=builder /app/.next ./.next | |
| COPY --from=builder /app/public ./public | |
| COPY --from=builder /app/package*.json ./ | |
| COPY --from=deps /app/node_modules ./node_modules | |
| USER nextjs | |
| EXPOSE 3000 | |
| CMD ["npm", "start"] |
도커 restart 옵션 추가
Summary by CodeRabbit