70. Next.js project deploy to docker container
Next.js ํ๋ก์ ํธ ๋์ปค ์ปจํ ์ด๋์ ๋ฐฐํฌํ๊ธฐ
์์ฑ์ผ
5/3/2025์์ฑ
ํํ์์ ์ผ
5/3/2025Next.js ํ๋ก์ ํธ ๋ฐฐํฌ
docker-compose up -d --build
์ฝ๋
# -- docker-compose.yaml -- #
services:
nextjs-frontend:
# ์ด๋ฏธ์ง ์ด๋ฆ ์ง์ (์ํ๋ ๋๋ก ๋ณ๊ฒฝ ๊ฐ๋ฅ)
image: vivakr-com:${TAG:-latest} # ํ๊ทธ ๊ธฐ๋ณธ๊ฐ์ latest, ํ๊ฒฝ ๋ณ์ TAG๋ก ์ง์ ๊ฐ๋ฅ
container_name: vivakr-com
build:
context: .
dockerfile: Dockerfile
# ๋น๋ ์ ์ฌ์ฉํ ํ๊ฒฝ ๋ณ์ (ARG) ์ ๋ฌ
# .env ํ์ผ์ NEXT_PUBLIC_* ๋ณ์๋ค์ ์ฌ๊ธฐ์ ์ถ๊ฐ
# docker compose build --build-arg NEXT_PUBLIC_API_URL=... ํํ๋ก๋ ์ ๋ฌ ๊ฐ๋ฅ
args:
NEXT_PUBLIC_API_URL: ${NEXT_PUBLIC_API_URL:-https://api.vivakr.com} # ํ๊ฒฝ ๋ณ์ ์์ผ๋ฉด ๊ธฐ๋ณธ๊ฐ ์ฌ์ฉ
NEXT_PUBLIC_IPINFO_URL: ${NEXT_PUBLIC_IPINFO_URL:-https://ipinfo.io}
NEXT_PUBLIC_IPINFO_URL2: ${NEXT_PUBLIC_IPINFO_URL2:-https://ns.vivakr.com}
NEXT_PUBLIC_CHAT_URL: ${NEXT_PUBLIC_CHAT_URL:-https://ns.kimbumjun.co.kr}
NEXT_PUBLIC_RUNNER_URL: ${NEXT_PUBLIC_RUNNER_URL:-https://runner.kimbumjun.com}
NEXT_PUBLIC_ENABLE_LOGIN_MENU: ${NEXT_PUBLIC_ENABLE_LOGIN_MENU:-true}
NEXT_PUBLIC_STORAGE_KEY: ${NEXT_PUBLIC_STORAGE_KEY:-user}
NEXT_PUBLIC_MYIP: ${NEXT_PUBLIC_MYIP:-} # ๋ก์ปฌ IP๋ ๋น๋ ์์ ์ ๋ฐ๋ผ ๋ฌ๋ผ์ง ์ ์์ด ์ฃผ์
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY: ${NEXT_PUBLIC_GOOGLE_MAPS_API_KEY:-}
NEXT_PUBLIC_GOOGLE_MAP_ID: ${NEXT_PUBLIC_GOOGLE_MAP_ID:-}
ports:
# Nginx๊ฐ ์ฐ๊ฒฐํ ํธ์คํธ ํฌํธ : ์ปจํ
์ด๋ ๋ด๋ถ ํฌํธ
- '47960:3000'
# ๋ฐํ์ ์ ํ์ํ ํ๊ฒฝ ๋ณ์ ์ฃผ์
# .env.local ๋ฐ NextAuth์ ํ์ํ .env ๋ณ์๋ค์ ์ฌ๊ธฐ์ ์ถ๊ฐ
environment:
# NextAuth ํ์ ํ๊ฒฝ ๋ณ์
NEXTAUTH_SECRET: ${NEXTAUTH_SECRET} # ํธ์คํธ ํ๊ฒฝ๋ณ์ ๋๋ .env ํ์ผ์์ ์ฝ์ด์ด
NEXTAUTH_URL: ${NEXTAUTH_URL:-https://vivakr.com} # ์ด์ ํ๊ฒฝ URL ๋๋ ๊ธฐ๋ณธ๊ฐ
# Google Provider
GOOGLE_CLIENT_ID: ${GOOGLE_CLIENT_ID}
GOOGLE_CLIENT_SECRET: ${GOOGLE_CLIENT_SECRET}
# GitHub Provider
GITHUB_CLIENT_ID: ${GITHUB_CLIENT_ID}
GITHUB_CLIENT_SECRET: ${GITHUB_CLIENT_SECRET}
# Facebook Provider
FACEBOOK_CLIENT_ID: ${FACEBOOK_CLIENT_ID}
FACEBOOK_CLIENT_SECRET: ${FACEBOOK_CLIENT_SECRET}
# Twitter Provider
TWITTER_CLIENT_ID: ${TWITTER_CLIENT_ID}
TWITTER_CLIENT_SECRET: ${TWITTER_CLIENT_SECRET}
# Discord Provider
DISCORD_CLIENT_ID: ${DISCORD_CLIENT_ID}
DISCORD_CLIENT_SECRET: ${DISCORD_CLIENT_SECRET}
# Microsoft Provider
MICROSOFT_CLIENT_ID: ${MICROSOFT_CLIENT_ID}
MICROSOFT_CLIENT_SECRET: ${MICROSOFT_CLIENT_SECRET}
# Next.js ๋ด๋ถ ํฌํธ ๋ณ๊ฒฝ ํ์ ์ (Dockerfile์ CMD์ ์ผ์น์์ผ์ผ ํจ)
# PORT: 3000
# ํธ์คํธ์ .env ํ์ผ ๋ก๋ (์ ํ์ , ๋ณด์ ์ฃผ์)
# ์ด๋ ๊ฒ ํ๋ฉด ์๋ environment ์น์
์์ ${VAR_NAME} ํํ๋ก ์ฌ์ฉ ๊ฐ๋ฅ
env_file:
- .env # ๋น๋ args์ฉ ๋ณ์๋ค ๋ก๋ (์ ํ์ )
- .env.local # ์ํฌ๋ฆฟ ๊ฐ๋ค ๋ก๋
# ์ฌ์์ ์ ์ฑ
restart: unless-stopped
# healthcheck ์ถ๊ฐ (์ ํ์ ์ด์ง๋ง ๊ถ์ฅ)
# healthcheck:
# test: [ 'CMD', 'curl', '-f', 'http://localhost:3000' ] # ์ฑ์ด ์๋ตํ๋์ง ๊ฐ๋จํ ํ์ธ
# interval: 30s
# timeout: 10s
# retries: 3
# start_period: 30s # ์ปจํ
์ด๋ ์์ ํ healthcheck ์์๊น์ง ๋๊ธฐ ์๊ฐ
# ๋คํธ์ํฌ ์ค์ (์ ํ ์ฌํญ, ๋ค๋ฅธ ์๋น์ค์ ์ฐ๊ฒฐ ์ ์ ์ฉ)
# networks:
# default:
# name: viv_nextjs_network
์ฐ๊ด์ฝ๋
# -- Dockerfile -- #
FROM node:slim AS builder
# ์์
๋๋ ํ ๋ฆฌ ์ค์
WORKDIR /app
# pnpm ์ค์น (Node ์ด๋ฏธ์ง์ ๊ธฐ๋ณธ ํฌํจ ์๋จ)
RUN npm install -g pnpm
# +++ OpenSSL ์ค์น ์ถ๊ฐ +++
# node:slim (Debian ๊ธฐ๋ฐ) ์ด๋ฏธ์ง์ OpenSSL ๊ฐ๋ฐ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
RUN apt-get update && apt-get install -y openssl libssl-dev && rm -rf /var/lib/apt/lists/*
# ์์กด์ฑ ํ์ผ ๋จผ์ ๋ณต์ฌ (์บ์ฑ ํ์ฉ)
COPY package.json pnpm-lock.yaml ./
# ํ๋ก๋์
์์กด์ฑ ํฌํจ ๋ชจ๋ ์์กด์ฑ ์ค์น
# pnpm fetch ๋ง์ผ๋ก๋ ๊ฐ๋ฅํ์ง๋ง, install ์ด ๋ ํ์คํ ์ ์์
RUN pnpm install --frozen-lockfile
# ๋๋จธ์ง ์์ค ์ฝ๋ ๋ณต์ฌ
COPY . .
# ํ๊ฒฝ ๋ณ์ ์ค์ (๋น๋ ์ ํ์ํ ์ ์์)
# ARG NEXT_PUBLIC_API_URL
# ENV NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL}
# ํ๋ก๋์
๋น๋ ์คํ (standalone output ์์ฑ)
RUN pnpm build
# ---- Stage 2: Production Runner ----
# ๋์ผํ Node.js Alpine ๋ฒ์ ์ ๋ฐํ์ ํ๊ฒฝ์ผ๋ก ์ฌ์ฉ
FROM node:slim AS runner
# +++ ๋ฐํ์์ฉ OpenSSL ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น (ํ์ํ ๊ฒฝ์ฐ) +++
# Prisma Client ๋ฐํ์์ด OpenSSL์ ํ์๋ก ํ ์ ์์
RUN apt-get update && apt-get install -y libssl3 && rm -rf /var/lib/apt/lists/*
# Debian 12(Bookworm) ๊ธฐ๋ฐ slim์ libssl3, ์ด์ ๋ฒ์ ์ libssl1.1
# ์์
๋๋ ํ ๋ฆฌ ์ค์
WORKDIR /app
# ํ๋ก๋์
ํ๊ฒฝ์์ ๋ช
์ (Next.js ์ต์ ํ)
ENV NODE_ENV=production
# ์ปจํ
์ด๋ ๋ด๋ถ์์ ์คํ๋ ํฌํธ (๊ธฐ๋ณธ๊ฐ 3000)
# ENV PORT=3000
# ๋น๋ ์คํ
์ด์ง์์ ํ์ํ ํ์ผ๋ง ๋ณต์ฌ
# standalone ๋น๋ ๊ฒฐ๊ณผ๋ฌผ ๋ณต์ฌ
COPY --from=builder /app/.next/standalone ./
# public ํด๋ ๋ด์ฉ ๋ณต์ฌ (์๋ค๋ฉด)
COPY --from=builder /app/public ./public
# static ๋น๋ ๊ฒฐ๊ณผ๋ฌผ ๋ณต์ฌ
COPY --from=builder /app/.next/static ./.next/static
# Prisma ์คํค๋ง ํ์ผ ๋ณต์ฌ (์ ํ์ ์ด์ง๋ง ๊ถ์ฅ)
COPY --from=builder /app/prisma ./prisma
# ์ดํ๋ฆฌ์ผ์ด์
์ด ์ฌ์ฉํ ํฌํธ ๋
ธ์ถ (๊ธฐ๋ณธ 3000)
EXPOSE 3000
# ์ปจํ
์ด๋ ์์ ์ ์คํ๋ ๋ช
๋ น์ด (standalone ์๋ฒ ์คํ)
CMD ["node", "server.js"]
CONCLUSION
Next.js project create Docker container
server {
listen 10001;
server_name vivakr.com;
return 301 https://vivakr.com$request_uri;
}
server {
listen 443 ssl;
server_name vivakr.com;
ssl_certificate /etc/letsencrypt/live/vivakr.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/vivakr.com/privkey.pem;
include /private/etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /private/etc/letsencrypt/ssl-dhparams.pem;
ssl_stapling on;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
access_log "/opt/homebrew/var/log/nginx/vivakr.com.access.log";
error_log "/opt/homebrew/var/log/nginx/vivakr.com.error.log" warn;
# HMR์ฉ WebSocket ์ค์
# location /_next/webpack-hmr {
# proxy_pass http://127.0.0.1:47960;
# proxy_http_version 1.1;
# proxy_set_header Upgrade $http_upgrade;
# proxy_set_header Connection "upgrade";
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# proxy_read_timeout 86400; # WebSocket ์ฐ๊ฒฐ ์ ์ง
# proxy_send_timeout 86400;
# proxy_buffering off;
# }
location / {
proxy_pass http://localhost:47960; # Next.js ๋ก์ปฌ ์๋ฒ๋ก ํ๋ก์
# proxy_pass http://192.168.0.8:3000; # Next.js ๋ก์ปฌ ์๋ฒ๋ก ํ๋ก์
proxy_http_version 1.1; # WebSocket ์ง์์ ์ํด ์ถ๊ฐ
proxy_set_header Upgrade $http_upgrade; # WebSocket ์
๊ทธ๋ ์ด๋ ํค๋
proxy_set_header Connection "upgrade"; # WebSocket ์ฐ๊ฒฐ ์ ์ง
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
์ญ์ ๊ถํ ํ์ธ ์ค...
์์ ๊ถํ ํ์ธ ์ค...