원래는 crud의 기초만 시도해보려했으나, docker 세팅을 먼저 해보고 싶어서 그 내용을 담아봤다.(처음 기록엔 crud를 위한 기록을 해놨음)
lottolist/
├── apps/
│ ├── web/ (Next.js 프론트엔드)
│ └── api/ (NestJS 백엔드)
└── packages/
├── database/ (Prisma 및 데이터베이스 관련 코드)
└── ui/ (공용 UI 컴포넌트)
현재 프로젝트 구조는 이렇게 되어있고
기본적인 세팅은 얼추 끝냈으니 당연하게도 작동이 잘되는지 풀스텍을 연동해서 게시글 만들기라도 하나 진행을 해봐야겠다
packages/prisma에 있는 schema.prisma 파일 안에
generator client { provider = "prisma-client-js" }
datasource db { provider = "postgresql" url = env("DATABASE_URL") }
model Post { id String @id @default(uuid()) title String content String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt }
이런식으로 초기 스키마 파일 내용을 구성했는데
추후 디테일한 내용은 테스트 이후 서서히 추가할 예정
ex - 카테고리, 상태 같은 정보
이후 packages/database/src/index.ts에서
import { PrismaClient } from '@prisma/client';
const prismaClientSingleton = () => {
return new PrismaClient();
};
declare global {
var prisma: undefined | ReturnType<typeof prismaClientSingleton>;
}
export const prisma = globalThis.prisma ?? prismaClientSingleton();
if (process.env.NODE_ENV !== 'production') globalThis.prisma = prisma;
export * from '@prisma/client';
해당 내용을 기입하는데,
싱글톤 패턴으로 작성되었다
import로 PrismaClient 즉 Prisma가 생성한 클라이언트 가져옴
const prismaClientSingleton = () => { return new PrismaClient(); };
PrismaClient의 새 인스턴스 생성
declare global { var prisma: undefined | ReturnType<typeof prismaClientSingleton>; }
전역 타입 선언하는데, globalThis.prisma의 타입이 먼지 알려줌
export const prisma = globalThis.prisma ?? prismaClientSingleton();
이미 전역 객체에 prisma 있으면 그것 사용하고, 없으면 새로 생성
그러면 애플리케이션 전체에서 단일 Prisma인스턴스 공유할 수 있음
여기서 Prisma는 node와 ts 애플리케이션에서 데이터베이스 작업 쉽게 해주는 ORM 도구여서 사용했는데
드디어 전에 적용하려다 안하던 DOCKER를 적용할 수 있게 되었다
Docker - 애플리케이션을 컨테이너라는 격리된 환경에서 실행하게 해주는 도구
컨테이너는 독립적이고 일관된 환경 제공
그래서 이 Docker를 사용하는 이유는 db인 PostgreSQL을 실행하기 위해서 인데,
일단 오늘은 도커 설치까지~
집가서 또 할수도있긴함
docker 를 이용하면코드 배송,테스트, 배포 시 실제 운영환경에서 실행하는 사이의 지연 시간을 크게 줄일 수 있다
일단 docker를 적용하기 위해 루트 디렉토리에 docker-compose.yml 파일을 생성해서
`version: '3.8'
services:
postgres:
image: postgres:latest
container_name: lotto-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: lottodb
ports:
volumes: postgres-data:
이런 내용을 채우라는데 처음써보니 분석이 필요하다
YAML Ain’t Markup Language 의 약자, 데이터를 표현하기 위한 간단한 형식
사람이 읽기 쉽게 설계, 구성 파일에 자주 사용
들여쓰기와콜론을 사용하여 데이터의 계층 구조 나타냄
여러 Docker컨테이너 정의하고 실행 위한 파일
여러 컨테이너 한번에 관리할 수 있게 해줌
컨테이너 - 독립된 환경에서 앱을 실행하는 가상화된 공간
version: '3.8' -- 강의에선 버전 적을 필요없다곤했는데 상관없을듯
services: 실행할 컨테이너들을 정의
postgres:
image: postgres:latest
container_name: lottodolist-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: lottodolistdb
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- lotto-network
postgres:latest 이미지를 사용
컨테이너 이름은 lottodolist-postgres로 지정
데이터베이스 계정/비밀번호/DB이름도 여기서 설정
근데 단계적으로 하나씩 세팅할 것이기에 lotto번호는 일단 제쳐두고 post관련 세팅으로 다시 구현해봤다.
postgres:
image: postgres:latest
container_name: post-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postdb
ports:
이후 추가적인 내용을 정리하자면
포트 5432(호스트):5432(컨테이너)연결-컴퓨터의 5432포트 접속 시 컨테이너 내부 DB로 연결된단 의미
postgres-data는 볼륨에 데이터를 저장한다는 의미로 컨테이너 삭제해도 데이터 유지시킬 수 있음
post-network - 다른 컨테이너와 통신 위해 해당 네트워크에 속한다는 의미
==============================
api:
build:
context: .
dockerfile: ./apps/api/Dockerfile
container_name: post-api
ports:
=============================
./apps/api/Dockerfile 생성했고 그 내용을 이용해서 이미지를 빌드한다 생각하면 된다.
지금은 컨테이너를 하나만 사용할거라 post관련된 post-api만 사용해볼 예정
포트는 백쪽이기에 3001(호스트):3000(컨테이너)로 연결
depends_on 의존성 관련된 내용같아서 postgres가 먼저 실행된 후에 이 서비스 시작하겠다는 의미
환경변수는 아직 지금 단계에선 건들건 아닌 것 같음
여기서의 볼륨 마운트는 소스코드 연결해주는건데 코드 수정 시 실시간 반영시켜준다고 함
그리고 디렉토리에서 네트워크 설정한 곳에 속한다고 네트워크까지 설정해준 모습
web:
build:
context: .
dockerfile: ./apps/web/Dockerfile
container_name: post-web
ports:
volumes:
postgres-data:
node_modules_api:
node_modules_web:
networks:
post-network:
driver: bridge
이미지 빌드는 비슷하게 ./apps/web/Dockerfile로 파일 생성
컨테이너 이름은 다 연관되어있긴하지만 구분 지을 수 있게 post-web
depends_on은 백코드가 먼저 실행 후에 서비스 시작하도록 세팅
volumes:
postgres-data:
node_modules_api:
node_modules_web:
볼륨엔 어떤 내용을 담을지 아직 모르겠음
일반적으로는 postgres-data ⇒ db데이터 보존
node 이런건 각 서비스의 node_modules 보존을 위해 적는 듯
networks:
post-network:
driver: bridge
bridge는 기본 네트워크 유형 타입
이렇게 구분지어서 설정하면
마이크로서비스 구조 - DB, 백엔드, 프론트엔드 분리해 각각 독립적 개발/배포가 가능
개발 편의성 - 코드 변경 시 볼륨 마운트로 실시간 반영 가능
의존성 관리 - depends_on으로 실행 순서 지정 DB→API→웹
환경 일관성 - 개발자 모두(물론 난 혼자) 동일한 개발 환경 사용 가능
데이터 지속성 - 볼륨으로 컨테이너 재시작해도 데이터 유지시킴
이제 POST 게시 테스트를 위해서
일단 이중에 게시물 작성하고 보는것 먼저 시도해볼 것 같다.
일단
Dockerfile은
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN pnpm install
COPY . .
EXPOSE 3000
CMD ["[pnpm", "run", "dev"]
web/api 둘다 이렇게 세팅해주고
Docker Desktop을 실행하고
터미널에서 docker-compose up 으로 실행을 진행해봤더니 에러가 발생했다
$ docker-compose up
time="2025-04-04T17:30:51+09:00" level=warning msg="C:\\Users\\gyp93\\Desktop\\mingyu\\lottolist\\docker-compose.yml: the attribute version is obsolete, it will be ignored, please remove it to avoid potential confusion"
[+] Running 10/14
...
...
...
...
=> ERROR [api 4/5] RUN pnpm install 0.5s
> [api 4/5] RUN pnpm install:
0.412 /bin/sh: pnpm: not found
>
---
failed to solve: process "/bin/sh -c pnpm install" did not complete successfully: exit code: 127
FROM node:18-alpine
WORKDIR /app
RUN npm install -g pnpm
COPY package*.json ./
RUN pnpm install
COPY . .
EXPOSE 3000
CMD ["pnpm", "run", "start:dev"]
이렇게 pnpm을 추가를 안해서 문제가 발생한거라 설치하는걸 추가해주었다
docker-compose up 명령어를 실행했을 때 수우우우우우많은 에러를 겪었는데
api:
build:
context: .apps/api # 오타: .apps/api가 아닌 ./apps/api여야 함
그래서 현재 웹 서비스Nest.js는 정상적으로 로컬호스트 3000번에서 잘 실행이 되고
API서비스인 NestJS는 TS오류가 발생중이다
해당 부분은 내일 혹은 다음주에 건들여서 다시 해결해보도록 하겠다.. 하 도커 이 요물녀석..
==================================
지금 내 폴더 구조는 모노레포 구조에 web/api/database 나눠놨는데
api에서 prisma가 없고 지금 현재는 web/api의 Dockerfile만 작성해놨기 때문에 prisma를 찾을 수 없는건 당연한 거였다.
근데 찾다보니
docker-compose.yml에서 api쪽의 볼륨에서 이미 packages쪽을 추가시켜놨는데 컨테이너 내부에 추가가 안되고 있던건 확실히 문제상황이다
일단은 당장 문제 해결을 위해
## packages/database 폴더에서 의존성 설치
cd /app/packages/database
pnpm install
## 설치 후 다시 시도
npx prisma db push
이 방법으로 성공하긴 했다
수동 성공이였기에 원인을 찾아보자면
어쨋든 prisma는 api쪽에서 사용해야할 기능이고,(아직 web쪽은 모르겟음) ui는 web쪽에서 사용해야할 패키지인데 그걸 설치를 하기위해 각각 web,api의 Dockerfile에서 해줘야 진행이 될것같다고 생각함
FROM node:18-alpine
WORKDIR /app
RUN npm install -g pnpm
COPY . .
RUN cd /app/packages/ui && pnpm install
RUN pnpm install
EXPOSE 3000
CMD ["pnpm", "run", "dev"]
웹쪽엔 ui쪽 패키지 설치 추가
FROM node:18-alpine
WORKDIR /app
RUN npm install -g pnpm
COPY . .
RUN cd /app/packages/database && pnpm install && npx prisma generate
RUN pnpm install
EXPOSE 3000
CMD ["pnpm", "run", "dev"]
api쪽엔 database쪽 설치 추가했고
version: '3.8'
services:
# PostgreSQL 데이터베이스 서비스
postgres:
image: postgres:latest
container_name: post-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postdb
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- post-network
# API 서버 (NestJS)
api:
build:
context: .
dockerfile: ./apps/api/Dockerfile
container_name: post-api
ports:
- "3001:3000"
depends_on:
- postgres
environment:
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postdb?schema=public
- NODE_ENV=development
volumes:
- ./apps/api:/app/apps/api
- ./packages:/app/packages
- /app/packages/database/node_modules
- /app/packages/ui/node_modules
- /app/node_modules
networks:
- post-network
# 웹 프론트엔드 (Next.js)
web:
build:
context: .
dockerfile: ./apps/web/Dockerfile
container_name: post-web
ports:
- "3000:3000"
depends_on:
- api
environment:
- NEXT_PUBLIC_API_URL=http://api:3000
- NODE_ENV=development
volumes:
- ./apps/web:/app
- ./packages:/app/packages
- /app/packages/database/node_modules
- /app/packages/ui/node_modules
- /app/node_modules
networks:
- post-network
volumes:
postgres-data:
node_modules_api:
node_modules_web:
networks:
post-network:
driver: bridge
docker-compose.yml엔 api와 web의 경로를 context가 아닌 dockerfile에 넣어주고 context엔 .만 추가해줬는데
context - 빌드 과정에서 Docker 데몬에 전송되는 디렉토리 지정. 이 디렉토리의 모든 파일은 빌드 과정에서 사용할 수 있음. Docker는 이 경로의 모든 파일을 빌드 컨텍스트로 사용
dockerfile - 사용할 Dockerfile의 이름과 경로 지정. 이 경로는 context에 상대적
기존 context에 있던 경로를 dockerfile쪽으로 옮긴 이유는
모노레포 구조에선 여러 애플리케이션이 공유 패키지 사용함
API, 웹 서비스 모두 packages 디렉토리에 있는 패키지에 접근해야 함
context의 .을 설정 시 Dockerfile에서 루트 디렉토리의 모든 파일에 접근할 수 있음 —- 이부분때문에 packages의 패키지에 접근하려고 .으로 설정을 바꿈
즉, context는 어떤 파일들이 빌드에 사용되는지 결정, dockerfile은 어떤 빌드 스크립트를 사용할지 결정하는 것임
그렇게 설정 시 Dockerfile에서 COPY . . 명령 사용 시 루트 디렉토리의 모든 파일이 복사되어 packages디렉토리에 접근권한이 생김
그러나 그러고 docker-compose down이후 docker-compose up —build를 진행해보니
failed to solve: error from sender: open C:\Users\gyp93\Desktop\mingyu\lottolist\packages\database\node_modules\.pnpm\node_modules\@esbuild\linux-x64: The file cannot be accessed by the system.
없던 에러가 발생했는데
Windows 시스템에서 Linux 전용 파일에 접근하려 할때 발생하는 문제라고 함
원인은
근데 그이후로 계~~속 에러가 떠서
그냥 한번에 총정리하려고 여러번 시도했다
[8:49:13 AM] Starting compilation in watch mode...
post-api |
post-web | ▲ Next.js 15.2.4 (Turbopack)
post-web | - Local: [http://localhost:3000](http://localhost:3000/)
post-web | - Network: [http://172.18.0.4:3000](http://172.18.0.4:3000/)
post-web |
post-web | ✓ Starting...
post-web | Attention: Next.js now collects completely anonymous telemetry regarding usage.
post-web | This information is used to shape Next.js' roadmap and prioritize features.
post-web | You can learn more, including how to opt-out if you'd not like to participate in this anonymous program, by visiting the following URL:
post-web | https://nextjs.org/telemetry
post-web |
post-web | ✓ Ready in 4.5s
post-api | [8:49:21 AM] Found 0 errors. Watching for file changes.
post-api |
post-api | [Nest] 30 - 04/05/2025, 8:49:22 AM LOG [NestFactory] Starting Nest application...
post-api | [Nest] 30 - 04/05/2025, 8:49:22 AM LOG [InstanceLoader] AppModule dependencies initialized +21ms
post-api | [Nest] 30 - 04/05/2025, 8:49:22 AM LOG [RoutesResolver] AppController {/}: +12ms
post-api | [Nest] 30 - 04/05/2025, 8:49:22 AM LOG [RouterExplorer] Mapped {/, GET} route +9ms
post-api | [Nest] 30 - 04/05/2025, 8:49:22 AM LOG [NestApplication] Nest application successfully started +5ms
v View in Docker Desktop o View Config
w Enable Watch
하……….. 드디어 됐네 ㅠ
거진 down && up을 10번은 넘게해서 드디어 해결된 것같은데 중간부터는 도저히 내가 해결할 수 없어서 그냥 복붙해서 계속 시도만 하였다
정리를 해보자면
failed to solve: error from sender: open C:\Users\gyp93\Desktop\mingyu\lottolist\.apps\api
failed to solve: error from sender: open C:\Users\gyp93\Desktop\mingyu\lottolist\packages\database\node_modules\.pnpm\node_modules\@esbuild\linux-x64
volumes:
- ./apps/api:/app/apps/api
- ./packages:/app/packages
- /app/packages/database/node_modules
- /app/node_modules
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND In packages/ui: "@repo/eslint-config@workspace:*" is in the dependencies but no package named "@repo/eslint-config" is present
Error: Cannot find module '/app/node_modules/next/dist/bin/next'
Error: Cannot find module '/app/apps/api/node_modules/@nestjs/cli/bin/nest.js'
최종 해결책
services:
# PostgreSQL 데이터베이스 서비스
postgres:
image: postgres:latest
container_name: post-postgres
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
POSTGRES_DB: postdb
ports:
- "5432:5432"
volumes:
- postgres-data:/var/lib/postgresql/data
networks:
- post-network
# API 서버 (NestJS)
api:
build:
context: ./apps/api
dockerfile: Dockerfile
container_name: post-api
ports:
- "3001:3000"
depends_on:
- postgres
environment:
- DATABASE_URL=postgresql://postgres:postgres@postgres:5432/postdb?schema=public
- NODE_ENV=development
volumes:
- ./apps/api/src:/app/src
networks:
- post-network
# 웹 프론트엔드 (Next.js)
web:
build:
context: ./apps/web
dockerfile: Dockerfile
container_name: post-web
ports:
- "3000:3000"
depends_on:
- api
environment:
- NEXT_PUBLIC_API_URL=http://api:3000
- NODE_ENV=development
volumes:
- ./apps/web/src:/app/src
- ./apps/web/public:/app/public
networks:
- post-network
volumes:
postgres-data:
networks:
post-network:
driver: bridge
FROM node:18-alpine
# NestJS CLI 전역 설치
RUN npm install -g @nestjs/cli
WORKDIR /app
# 패키지 파일만 복사
COPY package.json .
COPY tsconfig.json .
COPY nest-cli.json .
# 의존성 설치
RUN npm install
# 소스 코드 복사
COPY src/ ./src/
# 개발 서버 실행
CMD ["npm", "run", "dev"]
FROM node:18-alpine
WORKDIR /app
# 패키지 파일만 복사
COPY package.json .
COPY tsconfig.json .
COPY next.config.ts .
# 의존성 설치
RUN npm install
# 소스 코드 복사
COPY src/ ./src/
COPY public/ ./public/
# 개발 서버 실행
CMD ["npm", "run", "dev"]
결국 수많은 시행착오를 겪고, 모노레포에 Docker 적용을 성공하긴했지만, Dockerfile도 2개고 각각 package.json 위치가 제각각이여서 앞으로 내용을 추가하다보면 또 다시 에러를 많이 직면할 것 같다.
그리고 볼륨 마운트 즉, 실시간 개발 환경 구축을 위한 부분에서 node_modules를 컨테이너 내부에서 관리하도록 처리해야하는데 외부에서 설치시도하고 했던게 문제가 되었던 것 같음
그리고 세팅하다보니 lunux환경에서만 작동하는 파일 시스템도 존재했어서 크로스 플랫폼 이슈 피하기 위해 .dockerignore 파일도 세팅했어야 했다.
그리고 각 서비스를 하나하나 docker-compose에 넣어줘서 독립적으로 작동하게 구현이 되었던 것도 큰 것 같다.
일단 최초 세팅인 Docker에 대해선 성공했으니, 나머지는 실제 구현하면서 에러를 잡아가보도록 해보자~!