>
서버 역할을 할 PC를 하나 사서, 이걸로 AI 학습도 시키고, 내 사이트도 온라인에 배포하고, 내 서비스와 통신하면서 뭔가 처리할 API 역할도 하게 하고 싶다.
[참고]
맥스튜디오로 서버를 구축함.
그렇기에 윈도우는 명령어와 방법이 일부 다를 수 있음.
[목표]
1. 사이트 배포 (api 통신도 겸할 도메인)
2. DB 구축 (NoSQL DB, vector DB) : 기존에 사용하던 MongoDB 데이터와 pinecone에 있는 데이터 이전도 필요함.
3. sLLM 을 활용한 버티컬 AI 생성
4. sLLM을 똑똑하게 만들 파인튜닝 학습 (w. LoRA)
5. 구축된 DB를 RAG로 만들어 sLLM이 맞춤형 응답 하도록 하기.
사실 코드를 그냥 서버용PC에서 만든 다음 라우터만 설정해서 배포하면 끝나는 일인데, 그렇게 되면 코드를 수정할 때마다 서버용PC에서 수정해야 하잖아?
그건 너무 귀찮으니 다음과 같은 플로우로 진행되도록 했다.
[운영 플로우]
1. 깃허브에 파일을 올린다(Privae 으로.)
2. 깃허브에 올린 파일이 업데이트 되면, 도커에서 자동으로 감지한다.
- 단, main 브랜치로 merge 한 경우에만.
3. 그리고 도커에서 수정된 깃허브 파일로 코드를 자동 반영한다.
아참, 그리고 난 사이트가 3개 이상 되니, 여러 사이트를 연결해야 한다.
그래서 아키텍처를 “GitHub(main) → 이미지 빌드/푸시(GHCR) → 서버(Docker)에서 자동 갱신(Watchtower) → 리버스프록시(Traefik)로 도메인 라우팅” 으로 잡게 구성했다.
** 참고 : 난 공용 사무실을 사용하기에 공유기 설정을 직접 바꿀 순 없었다. 그래서 cloudflare를 통해 DNS설정, 네임서버 까지 바꿔야 했다.
~/server/
traefik/ # 도메인 라우팅 + HTTPS
docker-compose.yml
letsencrypt/
acme.json
sites/ # 웹사이트 3개
site1/
docker-compose.yml # 또는 앱 전용 설정
.env
site2/
docker-compose.yml
.env
site3/
docker-compose.yml
.env

A안) 스택 분리
B안) 하나의 거대 compose
난 A안으로 진행했다.
docker network create web
docker network ls | grep web
- 서버 PC의 터미널에서 실행
- 모든 서비스 compose에서 networks: web: external: true로 동일 네트워크를 공유.
~/server/traefik/ 폴더에서 진행.
(1) 인증서 폴더/파일 준비(이미 했으면 넘어가도 됨)
cd ~/server/traefik
mkdir -p letsencrypt
touch letsencrypt/acme.json
chmod 600 letsencrypt/acme.json
(2) docker-compose.yml 만들기
cat > docker-compose.yml << 'EOF'
services:
traefik:
image: traefik:v3.6.4
container_name: traefik
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
- --entrypoints.websecure.address=:443
# Let's Encrypt (HTTP-01)
- --certificatesresolvers.le.acme.httpchallenge=true
- --certificatesresolvers.le.acme.httpchallenge.entrypoint=web
- --certificatesresolvers.le.acme.email=dailist.live.well@gmail.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --providers.docker.endpoint=unix:///var/run/docker.sock
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/letsencrypt
networks:
- web
watchtower:
image: containrrr/watchtower:1.7.1
container_name: watchtower
command: --debug --interval 60 --cleanup --label-enable
environment:
- DOCKER_API_VERSION=1.44
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./ghcr-config.json:/config.json:ro
restart: unless-stopped
networks:
- web
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
command: tunnel --no-autoupdate run --token ${CF_TUNNEL_TOKEN}
restart: unless-stopped
networks:
- web
networks:
web:
external: true
EOF
- 혹은 VScode와 같은 IDE프로그램을 실행시켜, traefik 폴더에서 docker-compose.yml 파일을 생성 > 위 코드를 붙여넣으면 된다. 단, 1번째와 맨 마지막 코드만 빼고. 이건 터미널 실행 명령어라서 붙여넣으면 안된다.
(3) 실행
docker compose up -d
docker compose ps
docker logs -f traefik
services:
도메인:
image: ghcr.io/깃허브ID/레포지토리명:latest
container_name: 컨테이너명
labels:
- "traefik.enable=true"
# Traefik v3: Host()에 콤마로 2개 넣지 말고 OR로 연결
- "traefik.http.routers.구분명.rule=Host(`도메인.com`) || Host(`www.도메인.com`)"
- "traefik.http.routers.구분명.entrypoints=web"
# 컨테이너 내부 포트(Flask/Gunicorn)
- "traefik.http.services.구분명.loadbalancer.server.port=8000"
# watchtower 자동업데이트 대상
- "com.centurylinklabs.watchtower.enable=true"
networks:
- web
- 도메인 : 만약 네이버라면 naver 만 입력
- 레포지토리명 : 깃허브 레포지토리 명칭을 넣으면 됨. 단, 대문자는 입력하면 안됨.
- 컨테이너명 : 구분하기 쉽게 알아서 지정.
- 도메인은 구매한 url 주소를 입력하면 됨.
- 구분명은 GPT에게 물어보면 빠르게 정리해줄 거임
(1) Dockerfile
FROM python:3.12-slim
WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
# requirements.txt가 있으면 그걸 쓰는 게 가장 좋음
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
COPY . /app
EXPOSE 8000
CMD ["gunicorn", "-b", "0.0.0.0:8000", "app:app"]
- 루트디렉토리 최상단에 파일 추가(확장자 없음)
- 만약 파이썬으로 코드 짤 때, createdApp()같은 함수로 시작했다면, 맨 마지막 CMD 영역에 "app:createdApp()"으로 수정해야함.
(2) .dockerignore (선택) > 난 올릴 도커이미지가 많아서 용량 줄이려고 했다.
.DS_Store
__pycache__/
*.pyc
.venv/
venv/
node_modules/
.git
.gitignore
- 루트 디렉토리 최상단에 파일 추가 (확장자 없음)
(3) /.github/workflows/docker-publich.yml
폴더 생성 후 docker-publish.yml 파일 생성
name: Build and Push (main -> GHCR)
on:
push:
branches: [ "main" ]
permissions:
contents: read
packages: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v3
- name: Login to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build & Push (multi-arch)
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64,linux/arm64
tags: ghcr.io/깃허브ID/레포지토리명:latest
- 맨 마지막 tags를 기준으로 어떤 깃허브 액션이 동작하는지에 따라 업데이트 됨.
(4) requirements.txt
Flask
gunicorn
requests
beautifulsoup4
pymongo
openai
pinecone
python-dotenv
google-cloud-firestore
urllib3
dnspython
flask-cors
certifi
firebase-admin
Pillow
pytz
apscheduler
pdfminer.six
PyPDF2
numpy
psutil
pandas
- 루트디렉토리 최상단에 파일 추가(확장자 txt)
- 사용하는 패키지가 있다면 다 입력하면 됨.
- 버젼 명시를 하면 좋긴한데, 안해도 무방함.
- 최소한 Flask, guicorn은 해야함.
- 이걸 하는 이유는, 사이트마다 사용하는 패키지가 다르고, 그 패키지를 도커 이미지에서 미리 묶어서 관리하기 위함임. 즉, 이미지에 미리 설치해둔다라고 생각하면 됨. 그렇기에 이미지를 만들때마다 requirements.txt는 항상 들어감.
(1) 로그인
docker login ghcr.io
- Username : 깃허브ID
- Password : GitHub Personal Access Token(PAT)
(2) Password에 PAT를 입력한다.
[PAT 찾는 방법]
1) 깃허브에 접속한다 > 그리고 프로필을 누른다 > Settings 버튼을 누르면 나오는 페이지의 좌측 하단에 Developer settings 메뉴를 누른다.

2) Personal access token에서 Tokens(classic) 버튼을 누른 후 우측에 있는 [Generte new token]을 누른다. 그 다음 Generate new token (classic)을 선택한다.

3) Note는 제목이니까 아무렇게나 짓고, repo와 write:packages를 모두 누른다.

4) 그 다음 하단의 Generate Token 버튼을 눌러 저장하면 아래와 같이 토큰이 뜬다. (난 테스트로 캡쳐하려고 만든거라, 아래와 같이 뜨는데, 원래 토큰만 뜨는게 맞다)

(1) Cloudflare 사이트에 접속한다.
- https://dash.cloudflare.com/
(2) 도메인을 입력한다.

- 도메인을 입력 후 하단의 "다음"버튼을 누른다.
(3) 요금제를 선택한다.

- 난 Free로 선택했다.
(4) DNS 레코드를 검토한다.

- 쉽게말해 도메인 구매사이트(난 가비아에서 설정함)에서 DNS 설정에 "타입"이 A, CNAME 이 있는 걸 삭제한다.
- 사이트 배포를 위해 설정해놓은 A, CNAME 설정만 삭제해야한다. host 가 주로 @이거나 www인 것만. 나머지는 사이트 설정마다 달라서 잘 모르겠으면 구글링 후 안전한 것만 삭제해라.
(5) 네임서버를 바꾼 후 저장한다.

- 이제 네임서버를 다 지우고, 위에서 안내해준 cloudflare 네임서버를 붙여넣으면 된다. 기본으로 있던 네임서버는 다 지우면 된다.
- 이것도 난 가비아에서 설정함.
- 그 다음 "계속"버튼을 누르면 네임서버 확인까지 5-10분 걸리고, 그 다음 "빠른 시작 가이드" 같은게 뜨면 끝이다.
(1) Cloudflare Zero Trust에서 Tunnel 만들기

- 좌측 하단의 "Zero Trust" 메뉴 클릭

- 이동된 페이지의 "네트워크 > 커넥터"클릭 > "+ 터널 생성" 버튼 클릭

- Cloudflared (좌측) 터널 유형을 클릭(WARP Connector는 뭔지 모름)

- 그러면 위와 같이 뜨는데, 좌측 하단의 --token 어쩌구 명령에 있는 걸 복붙한다.
- token 뒤에 있는게 토큰이다. 이걸 복사해둔다.
(2) 토큰을 .env에 저장
cd ~/server/edge
cat > .env << 'EOF'
CF_TUNNEL_TOKEN=여기에_Cloudflare_토큰
EOF
chmod 600 .env
- (1)에서 복사한 토큰을 위에다 붙여넣고, 명령어를 입력한다.
(3) docker-compose.yml 만들기
services:
traefik:
image: traefik:v3.6.4
container_name: traefik
command:
- --providers.docker=true
- --providers.docker.endpoint=unix:///var/run/docker.sock
- --providers.docker.exposedbydefault=false
- --entrypoints.web.address=:80
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- web
watchtower:
image: containrrr/watchtower
container_name: watchtower
command: --interval 60 --cleanup --label-enable
volumes:
- /var/run/docker.sock:/var/run/docker.sock
networks:
- web
cloudflared:
image: cloudflare/cloudflared:latest
container_name: cloudflared
command: tunnel --no-autoupdate run --token ${CF_TUNNEL_TOKEN}
env_file:
- .env
restart: unless-stopped
networks:
- web
networks:
web:
external: true
(4) Cloudflare에서 호스트네임 연결하기

(4) 실행
cd ~/server/edge
docker compose up -d
docker compose ps
docker logs -f cloudflared
(1) 서버에서 사이트1 올리기
cd /server/sites/site1
docker compose up -d
docker compose ps
docker logs -f site1
(2) 서버에서 Traefik 라우팅 확인하기
curl -I -H "Host: 도메인.com" http://localhost
[원래 코드]
1. STEP6. 진행하기
2. 깃허브의 main 브랜치로 병합하기.
[서버]
1. 2번째 사이트 폴더에서 docker-compose.yml 파일 만들기 (STEP5. 참고)
2. 그리고 STEP9으로 실행/확인한다.
[Cloudflare]
1. STEP8의 DNS 설정, STEP9의 라우팅 설정을 한다. 단, STEP9은 1개의 터널에서 도메인만 추가하여 전환하도록 해도 된다.
| [docker] 서버 재기동&업데이트 명령어 (0) | 2026.01.08 |
|---|---|
| [docker] docker-compose.yml 수정 후 도커에 업데이트하기(명령어) (0) | 2025.12.28 |
| 앱 배포 시, 개발자 개인계정을 회사계정으로 바꾸고 싶다면?(구글 플레이, 앱스토어) (0) | 2025.11.05 |
| [플러터] 자주 사용하는 git 명령어 (0) | 2025.10.15 |
| [플러터] git 설정이 꼬였을 때, 완전 초기화! (0) | 2025.10.15 |