Skip to content

Commit c81b8a0

Browse files
authored
Merge pull request #112 from Team-Senifit/release-1.0.1
remove /api baseurl
2 parents cc474e2 + b7ee949 commit c81b8a0

34 files changed

Lines changed: 651 additions & 360 deletions

File tree

.dockerignore

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
.git
2+
.gitignore
3+
.next
4+
node_modules
5+
npm-debug.log
6+
Dockerfile
7+
docker-compose.yml
8+
.DS_Store
9+
README.md
10+
docs
11+
stories
12+
scripts
13+
.storybook
14+
**/*.log

.github/workflows/deploy.yml

Lines changed: 50 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -9,136 +9,69 @@ concurrency:
99
group: deploy-develop
1010
cancel-in-progress: true
1111

12+
permissions:
13+
contents: read
14+
packages: write
15+
1216
jobs:
1317
build-and-deploy:
1418
runs-on: ubuntu-latest
1519
env:
16-
APP_DIR: /srv/senifit-front
17-
RELEASE_NAME: ${{ github.sha }}
18-
NODE_ENV: production
19-
NEXT_PUBLIC_API_BASE: ${{ secrets.NEXT_PUBLIC_API_BASE }}
20-
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
21-
API_PREFIX: ${{ secrets.API_PREFIX }}
22-
NEXT_PUBLIC_SITE_URL: ${{ secrets.NEXT_PUBLIC_SITE_URL }}
20+
IMAGE_NAME: ghcr.io/team-senifit/web
21+
IMAGE_TAG: ${{ github.sha }}
2322

2423
steps:
2524
- name: Checkout
2625
uses: actions/checkout@v4
2726

28-
- name: Setup Node
29-
uses: actions/setup-node@v4
27+
- name: Log in to GHCR
28+
uses: docker/login-action@v3
3029
with:
31-
node-version: "22.x"
32-
cache: "npm"
33-
34-
- name: Install deps
35-
run: npm ci
36-
37-
- name: Build
38-
run: npm run build
39-
40-
# next는 dependencies에 있어야 함. devDeps는 제거해 산출물 슬림화
41-
- name: Prune devDependencies
42-
run: npm prune --omit=dev
43-
44-
- name: Create deploy package
45-
run: |
46-
set -euo pipefail
47-
mkdir -p deploy
48-
# 실행에 필요한 산출물만 포함 (.next, node_modules, 정적파일, 설정)
49-
cp -r .next node_modules package.json package-lock.json public next.config.* deploy/ 2>/dev/null || true
50-
tar -C deploy -czf release.tgz .
30+
registry: ghcr.io
31+
username: ${{ github.repository_owner }}
32+
password: ${{ secrets.GITHUB_TOKEN }}
5133

52-
- name: Add SSH key
53-
uses: webfactory/ssh-agent@v0.9.0
34+
- name: Build and push image
35+
uses: docker/build-push-action@v6
5436
with:
55-
ssh-private-key: ${{ secrets.EC2_SSH_KEY }}
56-
57-
- name: Known hosts
58-
run: |
59-
set -euo pipefail
60-
mkdir -p ~/.ssh
61-
ssh-keyscan -H "${{ secrets.EC2_HOST }}" >> ~/.ssh/known_hosts
62-
63-
- name: Remote prep (cleanup & prepare)
64-
env:
65-
EC2_USER: ${{ secrets.EC2_USER }}
66-
EC2_HOST: ${{ secrets.EC2_HOST }}
67-
run: |
68-
ssh "$EC2_USER@$EC2_HOST" bash -se <<'PREP'
69-
set -euo pipefail
70-
71-
echo "==== System Health Check ===="
72-
df -h /
73-
free -m
74-
75-
APP_DIR="/srv/senifit-front"
76-
echo "==== Cleanup Old Releases ===="
77-
# 폴더가 없으면 생성
78-
if [ ! -d "$APP_DIR/releases" ]; then
79-
sudo mkdir -p "$APP_DIR/releases" "$APP_DIR/shared"
80-
sudo chown $USER:$USER "$APP_DIR" "$APP_DIR/releases" "$APP_DIR/shared"
81-
fi
82-
83-
cd "$APP_DIR/releases"
84-
85-
# 최신 3개 제외 나머지 리스트업
86-
OLD_RELEASES=$(ls -1t | tail -n +4)
87-
88-
if [ -z "$OLD_RELEASES" ]; then
89-
echo "No old releases to clean up."
90-
else
91-
for rel in $OLD_RELEASES; do
92-
echo "Removing old release: $rel"
93-
# current와 중복 여부 체크 (안전을 위해)
94-
CURR="$(readlink -f "$APP_DIR/current" || true)"
95-
REL_PATH="$(readlink -f "$rel")"
96-
if [ "$REL_PATH" != "$CURR" ]; then
97-
rm -rf "$rel" || echo "Warning: Failed to remove $rel"
98-
else
99-
echo "Skipping current release: $rel"
100-
fi
101-
done
102-
fi
103-
104-
# current가 아닌 릴리즈 내부의 캐시 정리
105-
echo "==== Optimizing Passive Releases ===="
106-
CURR="$(readlink -f "$APP_DIR/current" || true)"
107-
for d in "$APP_DIR"/releases/*; do
108-
[ -d "$d" ] || continue
109-
if [ "$(readlink -f "$d")" != "$CURR" ]; then
110-
echo "Cleaning cache/modules in inactive release: $(basename "$d")"
111-
rm -rf "$d/.next/cache" "$d/node_modules" || true
112-
fi
113-
done
114-
PREP
37+
context: .
38+
file: Dockerfile
39+
push: true
40+
tags: ${{ env.IMAGE_NAME }}:${{ env.IMAGE_TAG }}
41+
build-args: |
42+
NEXT_PUBLIC_SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
43+
NEXT_PUBLIC_API_URL=${{ secrets.NEXT_PUBLIC_API_URL }}
44+
SITE_URL=${{ secrets.NEXT_PUBLIC_SITE_URL }}
45+
46+
- name: Configure AWS credentials
47+
uses: aws-actions/configure-aws-credentials@v4
48+
with:
49+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
50+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
51+
aws-region: ${{ secrets.AWS_REGION }}
11552

116-
- name: Remote deploy (stream extract, swap, restart)
53+
- name: Deploy via SSM
11754
env:
118-
EC2_USER: ${{ secrets.EC2_USER }}
119-
EC2_HOST: ${{ secrets.EC2_HOST }}
120-
RELEASE_NAME: ${{ env.RELEASE_NAME }}
121-
SERVICE_USER: ${{ secrets.SERVICE_USER }}
55+
SSM_INSTANCE_ID: ${{ secrets.SSM_INSTANCE_ID }}
56+
IMAGE_TAG: ${{ env.IMAGE_TAG }}
57+
IMAGE_NAME: ${{ env.IMAGE_NAME }}
12258
run: |
12359
set -euo pipefail
124-
APP_DIR="/srv/senifit-front"
125-
REL="${RELEASE_NAME}"
126-
SVC="${SERVICE_USER:-ubuntu}"
127-
128-
cat release.tgz | ssh -o StrictHostKeyChecking=yes "$EC2_USER@$EC2_HOST" \
129-
"set -euo pipefail; APP_DIR='$APP_DIR'; REL='$REL'; \
130-
sudo mkdir -p \"\$APP_DIR/releases/\$REL\"; \
131-
sudo chown \$USER:\$USER \"\$APP_DIR/releases/\$REL\"; \
132-
tar -xzf - -C \"\$APP_DIR/releases/\$REL\""
133-
134-
ssh -o StrictHostKeyChecking=yes "$EC2_USER@$EC2_HOST" \
135-
"set -euo pipefail; APP_DIR='$APP_DIR'; REL='$REL'; SVC='$SVC'; \
136-
echo '==== Finalizing Release ===='; \
137-
ln -sfn \"\$APP_DIR/releases/\$REL\" \"\$APP_DIR/current\"; \
138-
sudo systemctl daemon-reload; \
139-
sudo systemctl restart senifit-front"
140-
141-
- name: Public healthcheck (non-blocking)
142-
continue-on-error: true
143-
run: |
144-
curl -fsS -o /dev/null https://senifit.co.kr || echo "::warning::Public healthcheck failed"
60+
PARAMS=$(jq -nc --arg tag "$IMAGE_TAG" \
61+
'{commands:["cd /srv","export IMAGE_TAG="+$tag,"docker compose down","docker compose pull","docker compose up -d"]}')
62+
63+
COMMAND_ID=$(aws ssm send-command \
64+
--instance-ids "$SSM_INSTANCE_ID" \
65+
--document-name "AWS-RunShellScript" \
66+
--comment "Deploy web image $IMAGE_NAME:$IMAGE_TAG" \
67+
--parameters "$PARAMS" \
68+
--query "Command.CommandId" \
69+
--output text)
70+
71+
aws ssm wait command-executed \
72+
--command-id "$COMMAND_ID" \
73+
--instance-id "$SSM_INSTANCE_ID"
74+
75+
aws ssm get-command-invocation \
76+
--command-id "$COMMAND_ID" \
77+
--instance-id "$SSM_INSTANCE_ID"

Dockerfile

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
FROM node:22-alpine AS deps
2+
WORKDIR /app
3+
4+
COPY package.json package-lock.json .npmrc ./
5+
RUN npm ci
6+
7+
FROM node:22-alpine AS builder
8+
WORKDIR /app
9+
10+
COPY --from=deps /app/node_modules ./node_modules
11+
COPY . .
12+
13+
ARG NEXT_PUBLIC_SITE_URL
14+
ARG NEXT_PUBLIC_API_URL
15+
ARG SITE_URL
16+
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
17+
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
18+
ENV SITE_URL=${SITE_URL}
19+
ENV NEXT_TELEMETRY_DISABLED=1
20+
RUN npm run build
21+
RUN npm prune --omit=dev
22+
23+
FROM node:22-alpine AS runner
24+
WORKDIR /app
25+
26+
ENV NODE_ENV=production
27+
ENV NEXT_TELEMETRY_DISABLED=1
28+
ENV PORT=3000
29+
ENV HOSTNAME=0.0.0.0
30+
ENV NEXT_PUBLIC_SITE_URL=${NEXT_PUBLIC_SITE_URL}
31+
ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
32+
ENV SITE_URL=${SITE_URL}
33+
34+
COPY --from=builder /app/public ./public
35+
COPY --from=builder /app/.next ./.next
36+
COPY --from=builder /app/node_modules ./node_modules
37+
COPY --from=builder /app/package.json ./package.json
38+
COPY --from=builder /app/next.config.ts ./next.config.ts
39+
40+
EXPOSE 3000
41+
CMD ["npm", "run", "start"]
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,5 @@ const config = {
2121
lastmod: new Date().toISOString(),
2222
}),
2323
};
24-
export default config;
24+
25+
module.exports = config;

ops/docker-compose.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
version: "3.8"
2+
3+
services:
4+
web:
5+
image: ghcr.io/team-senifit/web:${IMAGE_TAG:-latest}
6+
environment:
7+
NODE_ENV: production
8+
NEXT_PUBLIC_SITE_URL: ${NEXT_PUBLIC_SITE_URL:-https://senifit.co.kr}
9+
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.senifit.co.kr:8443}
10+
volumes:
11+
- ./logs/nextjs:/app/logs/nextjs
12+
expose:
13+
- "3000"
14+
command: >
15+
sh -c "mkdir -p /app/logs/nextjs &&
16+
npm run start >> /app/logs/nextjs/app.log 2>&1"
17+
restart: unless-stopped
18+
19+
nginx:
20+
image: nginx:1.27-alpine
21+
depends_on:
22+
- web
23+
ports:
24+
- "80:80"
25+
- "443:443"
26+
volumes:
27+
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
28+
- ./nginx/conf.d:/etc/nginx/conf.d:ro
29+
- ./nginx/certs:/etc/nginx/certs:ro
30+
- ./logs/nginx:/var/log/nginx
31+
restart: unless-stopped

ops/logs/nextjs/app.log

Whitespace-only changes.

ops/logs/nginx/app.log

Whitespace-only changes.

ops/nginx/conf.d/default.conf

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
server {
2+
listen 80;
3+
server_name _;
4+
return 301 https://$host$request_uri;
5+
}
6+
7+
server {
8+
listen 443 ssl http2;
9+
server_name _;
10+
11+
ssl_certificate /etc/nginx/certs/fullchain.pem;
12+
ssl_certificate_key /etc/nginx/certs/private-key.pem;
13+
ssl_protocols TLSv1.2 TLSv1.3;
14+
ssl_prefer_server_ciphers on;
15+
16+
location / {
17+
proxy_pass http://web:3000;
18+
proxy_http_version 1.1;
19+
proxy_set_header Host $host;
20+
proxy_set_header X-Real-IP $remote_addr;
21+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
22+
proxy_set_header X-Forwarded-Proto https;
23+
proxy_set_header Upgrade $http_upgrade;
24+
proxy_set_header Connection $connection_upgrade;
25+
}
26+
}

ops/nginx/nginx.conf

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
user nginx;
2+
worker_processes auto;
3+
4+
error_log /var/log/nginx/app.log warn;
5+
pid /var/run/nginx.pid;
6+
7+
events {
8+
worker_connections 1024;
9+
}
10+
11+
http {
12+
include /etc/nginx/mime.types;
13+
default_type application/octet-stream;
14+
15+
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
16+
'$status $body_bytes_sent "$http_referer" '
17+
'"$http_user_agent" "$http_x_forwarded_for"';
18+
19+
access_log /var/log/nginx/app.log main;
20+
21+
sendfile on;
22+
keepalive_timeout 65;
23+
24+
map $http_upgrade $connection_upgrade {
25+
default upgrade;
26+
"" close;
27+
}
28+
29+
include /etc/nginx/conf.d/*.conf;
30+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"dev": "next dev --experimental-https --experimental-https-key ./certs/localhost-key.pem --experimental-https-cert ./certs/localhost.pem",
77
"proxy": "node scripts/dev-proxy.mjs",
88
"dev:https": "concurrently -k -n NEXT,SSL \"next dev -p 3001\" \"npm run proxy\"",
9-
"build": "next build && next-sitemap --config next-sitemap.config.mjs",
9+
"build": "next build && next-sitemap --config next-sitemap.config.js",
1010
"start": "next start",
1111
"typecheck": "tsc --noEmit",
1212
"lint": "next lint --max-warnings=0",

0 commit comments

Comments
 (0)