Skip to content

Conversation

@Hys-Lee
Copy link
Collaborator

@Hys-Lee Hys-Lee commented Aug 29, 2025

도커 restart 옵션 추가

Summary by CodeRabbit

  • Chores
    • Improved production reliability by enabling automatic restart for the application container, allowing the service to recover after unexpected crashes or host restarts.
    • No user-facing changes; deployment behavior remains the same with enhanced resilience and uptime.
    • Reduces downtime during incidents and simplifies operational recovery for more consistent availability.

Hys-Lee added 30 commits May 21, 2025 12:44
포크한 레포에 작동 테스틀 위해 연결
스토리북 형태도 세팅함
구조 수정 및 tailwind prefix에 맞게 mapping
이전 패키지 버전에선 빌드가 안 됐음.
Hys-Lee added 23 commits August 18, 2025 15:04
새로고침 막고, mutate처리 및 뒤로가기 정상화
todo 개수 0이 아닌 경우에 동작하도록 수정
api 경로에 따라 debounce하도록 수정 및 시간 제한 수정
기존에 수도 클래스로 적용하면 씹히는 경우가 있어서 props를 직접 사용하는 방식으로 수정
5개 넘어야 보이도록
goalId 동일한 것으로 잡도록 수정
dueDate도 계산해서 추가
게스트 모드라면 쿼리 키 다르게 해 refetch 유도
게스트 모드 기록 체크 및 기록이 있다면 온보딩 생략
중복되는 closeMSW함수 제거
도커 허브는 제 개인으로 해두겠습니답
@Hys-Lee Hys-Lee self-assigned this Aug 29, 2025
@coderabbitai
Copy link

coderabbitai bot commented Aug 29, 2025

Walkthrough

The 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

Cohort / File(s) Summary of Changes
CI/CD Workflow
\.github/workflows/production-deploy.yml
Added Docker run flag --restart always to ensure the motimo-fe-production container restarts automatically. No other command arguments or steps modified.

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
Loading

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~2 minutes

Poem

I nudge the ship with a careful hop,
A flag to rise when services drop.
Containers nap? They wake—no pause!
Restart always, deployment laws.
Thump-thump goes my rabbit heart—
Prod stays bright; we did our part. 🐇🚀

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 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.

📥 Commits

Reviewing files that changed from the base of the PR and between e139d51 and f1bbeb7.

📒 Files selected for processing (1)
  • .github/workflows/production-deploy.yml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .github/workflows/production-deploy.yml
⏰ 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
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbit in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbit in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbit gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbit read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbit help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbit ignore or @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbit summary or @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbit or @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@coderabbitai coderabbitai bot left a 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
+dist
app/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.

📥 Commits

Reviewing files that changed from the base of the PR and between 48e1d0e and e139d51.

📒 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
Copy link

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).

Comment on lines 20 to 21
uses: docker/login-action@v2
with:
Copy link

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.

Suggested change
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.

Comment on lines 26 to 30
- 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 }}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

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.

Suggested change
- 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

Comment on lines 33 to 49
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 }}
Copy link

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.

Suggested change
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.

Comment on lines 1 to 30
# 베이스 이미지로 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
Copy link

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.

Suggested change
# 베이스 이미지로 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"]

@Hys-Lee Hys-Lee merged commit c56d18d into prography:main Aug 29, 2025
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant