누군가에게 알려주기 보다는 나 스스로 정리 하며 언젠가 다시 사용할 때를 대비하는 글을 작성할것이다.
참고자료 : 내 경험 + AI
지난 1편에서 NAS에 Docker를 올리고 Jenkins를 통해 CI/CD 파이프라인을 구축하는 것으로 결정을 했다. 그럼 이제 이에 대한 개발을 시작해야 하는데 가장 먼 든 생각은 일단 개발과 운영을 분리를 하자 였다.
혼자쓰는 사이드 프로젝트라지만 엄염히 운영중인 서비스와 내가 막 코드를 수정하고 개발하는 개발 환경은 분리를 해야한다. 안 그러면 로컬에서 테스트 한다고 데이터를 지우거나 했을때 실제 운영 서비스가 바뀌는 대참사가 일어날 테니까 말이다.
그래서 본격적으로 뭔가를 구현하기에 앞서서 Docker Compose 하나로 빌드, 배포하고 환경 분리까지 처리하는 방법에 대해 정리하려 한다.
사이드라고 개발과 운영을 같이 섞어서 쓰는 순간 당장에 개발은 가능할지라도 후에는 답이 없어진다.
지금처럼 단순하게 만들기만 하는게 아닌 후에 운영도 해볼 생각이라면 분리를 하는게 맞다
뭐 물론 포스팅 하는것처럼 작성을 해보자면 이러한 이유들을 댈 수 있을것이다.
1. 데이터 오염 방지 : 테스트 데이터와 실 데이터가 섞이는 것 방지 가능
2. 안정성 확보 : 개발 서버의 테스트로 서버가 뻗어도 운영 서버는 살아있을 수 있다.
3. 보안 : 개발용 API Key와 운영용 API Key는 철저히 분리
그리고 지금처럼 내가 NAS라는 하나의 물리 장비 안에서 이 모든 걸 돌려야 하기에 물리적인 격리를 할 수 없으니 논리적인 격리가 더더욱 중요했다.
논리적인 격리가 중요하기에 이 문제를 해결하기 위해 Docker Compose와 .env 파일을 적극적으로 활용해보기로 했다.
다른 방법이 있을지도 모르겠지만 일단 내가 알고 있는 건 이 방법이기에 이걸로 하기로 했다.
일단 나스에서 컨테이너 매니저에서 프로젝트에 compose.yaml 파일을 만들어서 서비스를 정의하며 환경에 따라 실행되는 컨테이너를 다 다르게 가져갔다.
전략? 이라고 해야할까? 뭐라고 지칭을 해야 할지 모르겠어서 일단 전략이라고 하긴 했는데 일단 compose.ymal을 구성하면서 어떤걸 중점으로 할지 정했다.
이미지를 매번 로컬에서 빌드해서 올리는게 아니라 변경된 소스코드만 올려두면 NAS의 컨테이너를 사용해서 알아서 빌드하게 만들었다.
이렇게 하면 불필요한 빌드 도구들이 런타임 컨테이너에 남지 않게 되니 가볍고 깔끔하다 생각했다.
Compose 내에 두가지 환경을 모두 정의를 했다.
이게 진짜 환경 분리를 한 지점이라 보면 된다. 그리고 디버그용과 릴리즈용 두가지 환경에서 각각 빌드 컨테이너와 런 컨테이너 2개가 다 돌아가게 되어있다.
이거도 접기 써서 하면 블로그는 깔끔해질거 같은데 벨로그는 이걸 지원 안한다니까..
일단 코드만 그대로 올려두겠다.
지금 아래 있는 코드가 내가 사용하는 실제 코드랑은 많이 다를건데 잼미니한테 말해서 개인적인 정보 및 포트 사용하면서 바꾼것들 이름들 싹다 바꾼 그냥 가장 기본적인 내 방식에 맞는 코드니까 만약에 이걸 따라하실 분들이 계시다면 아래 코드를 가져다가 잘 바꿔서 써보면 된다.
코드에 대한 설명은 딱히 할 건 없을거 같고 이제 이걸 사용해서 실제 도커를 구동하는건 다음 포스트에 작성해보겠다.
version: '3.8'
networks:
app-network:
driver: bridge
services:
# ==============================================================================
# [1] DEBUG 환경 (개발용)
# ==============================================================================
frontend-build-debug:
container_name: frontend-build-debug
image: node:22-alpine
working_dir: /src
volumes:
- ./Client:/src
- ./Server:/back
- /src/node_modules
command: >
sh -c "\
rm -rf node_modules package-lock.json && \
npm install && \
npm run build && \
echo '[D] React Build DONE' && \
rm -rf /back/publish/debug/wwwroot/* || true && \
mkdir -p /back/publish/debug/wwwroot && \
cp -av /src/dist/. /back/publish/debug/wwwroot && \
echo '[D] Copy DONE' \
"
restart: "no"
networks:
- app-network
env_file:
- ./config/env/web.dev.env
backend-build-debug:
container_name: backend-build-debug
image: mcr.microsoft.com/dotnet/sdk:9.0
working_dir: /src
volumes:
- ./Server:/src
command: >
sh -c "
# MyApp.csproj는 본인의 프로젝트 파일명으로 변경
dotnet publish /src/MyApp/MyApp.csproj -c Debug -o /src/bin/Debug/net9.0
"
restart: "no"
networks:
- app-network
backend-run-debug:
container_name: backend-run-debug
image: mcr.microsoft.com/dotnet/aspnet:9.0
working_dir: /src/bin/Debug/net9.0
environment:
ASPNETCORE_ENVIRONMENT: "Development"
ASPNETCORE_WEBROOT: "/src/publish/debug/wwwroot"
ASPNETCORE_HTTP_PORTS: 8080
ASPNETCORE_URLS: "http://+:8080"
volumes:
- ./Server:/src
- ./Logs/API_Debug:/app/logs
ports:
- "8080:8080"
# 실행할 dll 파일명을 본인 프로젝트에 맞게 수정
command: ["dotnet", "MyApp.dll"]
restart: "no"
networks:
- app-network
depends_on:
backend-build-debug:
condition: service_completed_successfully
frontend-build-debug:
condition: service_completed_successfully
database:
condition: service_started
env_file:
- ./config/env/api.dev.env
# ==============================================================================
# [2] RELEASE 환경 (배포용)
# ==============================================================================
frontend-build-release:
container_name: frontend-build-release
image: node:22-alpine
working_dir: /src
volumes:
- ./Client:/src
- ./Server:/back
- /src/node_modules
command: >
sh -c "\
rm -rf node_modules package-lock.json && \
npm install && \
npm run build && \
echo '[R] React Build DONE' && \
rm -rf /back/publish/release/wwwroot/* || true && \
mkdir -p /back/publish/release/wwwroot && \
cp -av /src/dist/. /back/publish/release/wwwroot && \
echo '[R] Copy DONE' \
"
restart: "no"
networks:
- app-network
env_file:
- ./config/env/web.prod.env
backend-build-release:
container_name: backend-build-release
image: mcr.microsoft.com/dotnet/sdk:9.0
working_dir: /src
volumes:
- ./Server:/src
command: >
sh -c "
dotnet publish /src/MyApp/MyApp.csproj -c Release -o /src/bin/Release/net9.0
"
restart: "no"
networks:
- app-network
backend-run-release:
container_name: backend-run-release
image: mcr.microsoft.com/dotnet/aspnet:9.0
working_dir: /src/bin/Release/net9.0
environment:
ASPNETCORE_ENVIRONMENT: "Production"
ASPNETCORE_WEBROOT: "/src/publish/release/wwwroot"
ASPNETCORE_HTTP_PORTS: 80
ASPNETCORE_URLS: "http://+:80"
volumes:
- ./Server:/src
- ./Logs/API_Release:/app/logs
ports:
- "80:80"
command: ["dotnet", "MyApp.dll"]
restart: "no"
networks:
- app-network
depends_on:
backend-build-release:
condition: service_completed_successfully
frontend-build-release:
condition: service_completed_successfully
database:
condition: service_started
env_file:
- ./config/env/api.prod.env
# ==============================================================================
# [3] DB (MariaDB)
# ==============================================================================
database:
container_name: mariadb-server
image: mariadb:latest
restart: "no"
volumes:
- ./DB/config:/etc/mysql/conf.d
- ./DB/data:/var/lib/mysql
- ./Logs/DB:/var/log/mysql
environment:
TZ: Asia/Seoul
MYSQL_ROOT_PASSWORD: "${DB_ROOT_PASSWORD}"
MYSQL_TCP_PORT: 3306
ports:
- "3306:3306"
command: --log-error=/var/log/mysql/mariadb_error.log
networks:
- app-network
# ==============================================================================
# [4] RabbitMQ
# ==============================================================================
rabbitmq:
container_name: rabbitmq-server
hostname: rabbitmq
image: rabbitmq:3-management
restart: "no"
environment:
RABBITMQ_LOG_BASE: /var/log/rabbitmq
env_file:
- ./config/env/api.prod.env
ports:
- "5672:5672"
- "15672:15672"
volumes:
- ./MQ/data:/var/lib/rabbitmq
- ./Logs/MQ:/var/log/rabbitmq
networks:
- app-network
# ==============================================================================
# [5] Cache (Redis)
# ==============================================================================
redis:
container_name: redis-server
image: redis:alpine
restart: "no"
command: redis-server --appendonly yes --logfile /var/log/redis/redis.log --requirepass "${REDIS_PASSWORD}"
ports:
- "6379:6379"
volumes:
- ./Redis/data:/data
- ./Logs/Redis:/var/log/redis
networks:
- app-network
생각보다 코드가 너무 길어서 실제 만드는건 다음 포스팅에 해야 할 거 같다.
당연 틀린 부분 지적은 감사하나 비난은 정중하게 사양하겠다.