>

상세 컨텐츠

본문 제목

[docker] 내 PC를 서버로 만들기 1 - 사이트 배포하기

7. 봉드로이드_개발공부

by 마켓플레이어, 마케터 봉 2025. 12. 26. 12:29

본문

서버 역할을 할 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설정, 네임서버 까지 바꿔야 했다.

 

STEP1. 서버용 PC에 폴더 구조를 만든다.

~/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
  • 공통 인프라(리버스프록시/터널/자동업데이트): ~/server/traefik/
  • 사이트별 배포: ~/server/sites/<site_name>/ 폴더 생성.
  • AI, DB, 공용자산 등은 현재로썬 불필요하니 만들지 않음.

 

STEP2. “폴더 구조 ↔ 도커 구성” 연결방식 고민하기

A안) 스택 분리

  • traefik은 항상 켜둠
  • 각 사이트/AI/DB는 폴더별 docker compose up -d로 독립 운영
  • 장점: 장애 격리, 수정 영향 최소화, 운영 쉬움

 

B안) 하나의 거대 compose

  • ~/server/docker-compose.yml 하나에 전부 넣음
  • 장점: 한 번에 전체 올리기 쉬움
  • 단점: 수정할 때 실수 영향 범위가 큼

 

난 A안으로 진행했다.

 

 

STEP3. 공통 Docker 네트워크 (web) 생성 (Traefik과 모든 서비스들이 만날 수 있게.)

docker network create web
docker network ls | grep web

- 서버 PC의 터미널에서 실행

- 모든 서비스 compose에서 networks: web: external: true로 동일 네트워크를 공유.

 

 

STEP4. Traefik 스택 만들기/실행 (입구 + HTTPS + 자동업데이트 감시)

~/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

 

STEP5. 각 사이트 폴더에 docker-compose.yml 파일 만들기.

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에게 물어보면 빠르게 정리해줄 거임

 

STEP6. 배포할 사이트 코드에 아래 내용 추가하기.

(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는 항상 들어감.

 

 

STEP7. 도커에 깃허브 로그인하여 토큰 매칭하기

(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 버튼을 눌러 저장하면 아래와 같이 토큰이 뜬다. (난 테스트로 캡쳐하려고 만든거라, 아래와 같이 뜨는데, 원래 토큰만 뜨는게 맞다)

 

STEP8. 도메인 DNS 변경하기

(1) Cloudflare 사이트에 접속한다.

https://dash.cloudflare.com/ 

 

(2) 도메인을 입력한다.

- 도메인을 입력 후 하단의 "다음"버튼을 누른다.

 

(3) 요금제를 선택한다.

- 난 Free로 선택했다.

 

(4) DNS 레코드를 검토한다.

- 쉽게말해 도메인 구매사이트(난 가비아에서 설정함)에서 DNS 설정에 "타입"이 A, CNAME 이 있는 걸 삭제한다.

- 사이트 배포를 위해 설정해놓은 A, CNAME 설정만 삭제해야한다. host 가 주로 @이거나 www인 것만. 나머지는 사이트 설정마다 달라서 잘 모르겠으면 구글링 후 안전한 것만 삭제해라.

 

(5) 네임서버를 바꾼 후 저장한다.

- 이제 네임서버를 다 지우고, 위에서 안내해준 cloudflare 네임서버를 붙여넣으면 된다. 기본으로 있던 네임서버는 다 지우면 된다.

- 이것도 난 가비아에서 설정함.

- 그 다음 "계속"버튼을 누르면 네임서버 확인까지 5-10분 걸리고, 그 다음 "빠른 시작 가이드" 같은게 뜨면 끝이다.

 

STEP8. 중개 터널을 만든다.

(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에서 호스트네임 연결하기

  • 도메인1 http://traefik:80
  • www.도메인1  http://traefik:80
  • 도메인2  http://traefik:80
  • www.도메인2  http://traefik:80
  • 도메인3  http://traefik:80
  • www.도메인3  http://traefik:80
  • ...

 

(4) 실행

cd ~/server/edge
docker compose up -d
docker compose ps
docker logs -f cloudflared

 

 

STEP9. 이제 도커에 파일 올리기

(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

 

 

STEP10. 다른 사이트도 똑같이 올리기

[원래 코드]

1. STEP6. 진행하기

2. 깃허브의 main 브랜치로 병합하기.

 

[서버]

1. 2번째 사이트 폴더에서 docker-compose.yml 파일 만들기 (STEP5. 참고)

2. 그리고 STEP9으로 실행/확인한다.

 

[Cloudflare]

1. STEP8의 DNS 설정, STEP9의 라우팅 설정을 한다. 단, STEP9은 1개의 터널에서 도메인만 추가하여 전환하도록 해도 된다.

 

반응형

관련글 더보기