notes
Docker - Quick Starer (in turborepo)
Turborepo 프로젝트를 Docker로 시작하는 기본 구성·빌드 흐름 메모.
- 서버에는 무료인 Docker CE를 깔고
systemctl enable로 부팅 때 같이 뜨게 해 둔다. - 모노레포는
turbo prune web --docker로 필요한 패키지만 잘라서 멀티 스테이지로 빌드한다. - Next는 standalone 출력만 최종 이미지에 넣고, compose로 포트를 연결해 띄운다.
회사 서버에 turborepo로 만든 Next 앱을 처음 올리면서 적어 둔 메모다. Docker는 무료인 CE(Community Edition)와 기업 지원이 붙는 유료 버전이 있는데, 서버 한두 대 운영에는 CE로 충분했다.
Docker 서버 설치
start는 지금 한 번 띄우는 거고 enable은 재부팅 후에도 자동으로 뜨게 하는 거다. 처음엔 start만 해 두고 서버 재시작 뒤에 컨테이너가 다 죽어 있어서 놀랐다. 매번 sudo 치기 싫으면 sudo usermod -aG docker $USER 후 다시 로그인하면 된다.
# download doker
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
# doker-ce set
sudo yum install -y docker-ce
# doker in start
sudo systemctl start docker
# doker auto start able
sudo systemctl enable docker
#permission
sudo chown -R $(whoami) ~/.docker
Docker 데몬 설정
데몬 설정은 /etc/docker/daemon.json 하나에 모인다. 나는 사내망에서 이미지 pull이 DNS 때문에 실패해서 DNS를 박아 두었다.
- 설정 파일을 연다. 없으면 새로 만든다.
sudo vi /etc/docker/daemon.jsonDNS만 넣으면 이 정도다.
{ "dns": ["8.8.8.8", "8.8.4.4"] } - 저장하고 재시작해야 반영된다.
sudo systemctl restart docker
로그도 같이 제한해 두는 게 좋다. 기본 json-file 로그는 크기 제한이 없어서, 오래 떠 있는 컨테이너 하나가 디스크를 다 먹은 적이 있다. JSON이 깨지면 데몬이 아예 안 뜨니 저장 전에 쉼표를 확인한다.
{
"dns": ["8.8.8.8", "8.8.4.4"],
"log-driver": "json-file",
"log-opts": { "max-size": "10m", "max-file": "3" }
}
자주 쓰는 명령
# 실행중인 컨테이너 나열 :
sudo docker ps
# 모든 컨테이너 나열 (중지된 컨테이너 포함) :
sudo docker ps -a
# Docker Hub에서 이미지 가져오기 :
sudo docker pull nginx
# 이는 분리 모드에서 Nginx 컨테이너를 실행하여 호스트의 포트 8080을 컨테이너의 포트 80에 매핑합니다.
# 컨테이너 실행 :
sudo docker run -d -p 8080:80 nginx
# 실행중인 컨테이너 중지 :
sudo docker stop container_id_or_name
# 컨테이너 제거 :
sudo docker rm container_id_or_name
# 이미지 제거 :
sudo docker rmi image_id_or_name
# 테스트
sudo docker run --rm -it image_id_or_nameNext.js Docker
Next는 next.config에 output: 'standalone'을 주면 실행에 필요한 파일만 .next/standalone에 모아 준다. 그걸 복사해서 node server.js로 띄우면 node_modules 전체를 이미지에 넣지 않아도 된다.
아래 Dockerfile은 처음에 썼던 단순 복사 방식이다. 모노레포 전체를 install하다 보니 다른 앱 때문에 lockfile이 바뀌어도 매번 처음부터 설치했고, 그래서 아래 turbo prune 방식으로 갈아탔다.
FROM node:16
WORKDIR /usr/src/app
# Copy root package.json and lockfile
COPY package.json ./
COPY package-lock.json ./
# Copy the api package.json
COPY apps/api/package.json ./apps/api/package.json
RUN npm install
# Copy app source
COPY . .
EXPOSE 8080
CMD [ "node", "apps/api/server.js" ]# .dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
# 빌드
docker build -t my-nextjs-app .
# 실행
docker run -d -p 3000:3000 my-nextjs-app
turborepo Docker
turbo prune web --docker는 web 앱과 web이 의존하는 패키지만 남긴 작은 모노레포를 out/에 만들어 준다. 여기서 --docker가 핵심이다.
out/json에는 package.json들과 잘라낸 lockfile만 있어서, 이걸 먼저 COPY하고 install하면 소스만 바뀐 빌드에서는 설치 레이어 캐시가 그대로 쓰인다. 그다음 out/full을 복사해서 빌드한다. 마지막 runner 단계는 standalone 결과물과 static, public만 복사하고 root가 아닌 nextjs 유저로 띄운다.
# Docker file create
FROM node:18-slim AS base
# Set working directory
WORKDIR /app
# Install pnpm and turbo globally
RUN npm install -g pnpm turbo
FROM base AS builder
# Copy project files
COPY . .
# Generate a partial monorepo with a pruned lockfile for a target workspace
RUN turbo prune web --docker
FROM base AS installer
WORKDIR /app
# Copy the pruned lockfile and package.json files
COPY --from=builder /app/out/json/ .
RUN pnpm install
# Build the project
COPY --from=builder /app/out/full/ .
RUN pnpm turbo run build --filter=web...
FROM base AS runner
WORKDIR /app
# Don't run production as root
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
USER nextjs
# Automatically leverage output traces to reduce image size
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
COPY --from=installer --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
CMD ["node", "apps/web/server.js"]# .dockerignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
pnpm turbo prune web --docker # web 자리에 워크스페이스 이름 (예전 --scope 옵션은 이제 안 씀)
sudo docker build -t 컨테이너이름 .
모노레포에서 standalone을 쓰면 outputFileTracingRoot를 루트로 잡아 줘야 packages/* 코드까지 추적해서 넣어 준다. 아래는 당시 설정인데 output: 'standalone'이 빠져 있어서, 지금 다시 쓴다면 nextConfig에 그 줄부터 넣는다. Next 15부터는 outputFileTracingRoot도 experimental 밖 최상위 옵션으로 옮겨졌다.
// next.config.mjs or js
import path from 'path';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
/* transpilePackages: ['ui',"@ui"], */
async rewrites() {
return [
{
source: 'url넣어줘잉/:path*',
destination: `api 넣어줘잉`,
},
];
},
experimental:{
outputFileTracingRoot: path.join(path.dirname(__filename), `../../`)
}}
export default nextConfig;
version: '3.8'
services:
web:
image: smartq-app # 빌드된 Docker 이미지의 이름 또는 태그
build:
context: .
dockerfile: Dockerfile
# 13002(외부) 로 -> 3000 (내부 -> 프로젝트 내부 포트 변경시 해당부분 변경)
ports:
- "13002:3000"
environment:
NODE_ENV: production
command: ["node", "apps/web/server.js"]
networks:
- webnet
networks:
webnet:
1번은 볼륨까지 지우고, 7번은 떠 있는 컨테이너까지 전부 내린다. 운영 서버에서는 이 두 개를 치기 전에 한 번 더 본다.
# 1. 사용하지 않는 컨테이너, 네트워크, 이미지, 캐시, 볼륨 모두 제거
sudo docker system prune -a --volumes
# 2. 중지된 컨테이너만 제거
sudo docker container prune
# 3. 사용하지 않는 이미지 제거
sudo docker image prune -a
# 4. 사용하지 않는 볼륨 제거
sudo docker volume prune
# 5. 사용하지 않는 네트워크 제거
sudo docker network prune
# 6. Docker 빌드 캐시 제거
sudo docker builder prune
# 7. 모든 컨테이너 중지 및 제거
sudo docker stop $(sudo docker ps -a -q)
sudo docker rm $(sudo docker ps -a -q)배포를 몇 번 반복하면 이미지가 금방 쌓인다. 디스크가 차면 docker system df로 뭐가 큰지 먼저 보고, 위 prune 명령을 돌린다. 그래도 데몬이 이상하면 daemon.json을 백업해 두고 설정을 빼서 재시작해 본다. 매일 자동으로 정리하는 방법은 auto clean 글에 적어 뒀다.