slowlp
← 블로그
방법 2026.06.11 · 5분 읽기

집서버 1대에 앱 여러 개 올리기 — 단일 Caddy + web_edge 패턴 실전 가이드

주식앱·미술관·봇을 같은 서버에서 돌리는 구성, 있는 그대로 공개합니다

방법

집서버 1대에 앱 여러 개 올리기 — 단일 Caddy + web_edge 패턴 실전 가이드

주식앱·미술관·봇을 같은 서버에서 돌리는 구성, 있는 그대로 공개합니다

지금 제 집서버에는 세 개의 앱이 동시에 돌고 있습니다. 주식 포트폴리오 앱 trading_mvp, 온라인 미술관 한새암, 그리고 Discord ↔ 세컨드브레인 루프 봇 hermes-discord. 월 서버 비용은 0원입니다.

클라우드를 안 쓰는 게 처음부터 계획은 아니었어요. “일단 집에 돌고 있는 컴퓨터가 있으니 거기서 테스트해보자”로 시작했는데, 생각보다 잘 돌아가서 그냥 눌러앉았습니다.


문제: 앱이 늘어날 때마다 포트가 충돌한다

처음 앱 하나 올릴 때는 간단해요. 80 포트 열고 Caddy 띄우면 끝. 그런데 두 번째 앱을 올리는 순간부터 고민이 생깁니다. 같은 80/443을 두 앱이 쓸 수는 없으니까요.

흔히 보이는 해결책은 포트를 8080, 3000, 5000처럼 나눠 쓰는 것인데, 주소에 포트 번호가 붙어서 지저분하고 HTTPS 인증서도 앱마다 따로 관리해야 합니다. 더 큰 문제는 앱을 추가할 때마다 “이 포트 아직 비어 있나?” 확인하는 수작업이 반복된다는 거예요.


접근법: 단일 Caddy + web_edge 공유 네트워크

제가 쓰는 패턴은 이렇습니다.

Caddy 하나가 80/443을 독점하고, 외부에 노출해야 하는 앱은 web_edge라는 Docker 네트워크에 연결합니다. Caddy는 이 네트워크 안에서 컨테이너 이름으로 각 앱을 찾아 라우팅합니다.

인터넷 → Caddy(80/443) → web_edge 네트워크
                            ├── trading_mvp (컨테이너)
                            └── hansaiam    (컨테이너)

hermes-discord → Discord 게이트웨이 (아웃바운드만, web_edge 불필요)

Caddyfile은 단순합니다. 도메인별로 어느 컨테이너로 보낼지만 적으면 됩니다:

trading.duckdns.org {
    reverse_proxy trading_backend:8000
}

hansaiam.duckdns.org {
    reverse_proxy hansaiam_web:3000
}

새 앱을 추가하고 싶으면 docker-compose.yml에 web_edge 연결 두 줄 추가하고, Caddyfile에 도메인 블록 하나 넣으면 끝납니다. 포트 충돌 걱정 없이요.


각 단계

1단계: web_edge 네트워크 만들기

docker network create web_edge

한 번만 만들면 됩니다. 앱 컨테이너들은 이 네트워크에 연결만 하면 Caddy가 찾을 수 있습니다.

2단계: 신규 앱 배포 플레이북

ssh hong
git clone https://github.com/korat070/<앱이>.git ~/앱이름
cd ~/앱이름
cp .env.example .env
nano .env  # 시크릿 채우기

docker-compose.yml에 아래를 추가하면 web_edge에 연결됩니다:

networks:
  web_edge:
    external: true

그리고 docker compose up -d로 올리면 Caddy가 자동으로 인식합니다.

3단계: DuckDNS로 도메인 연결

여기서 한 가지 삽질 이야기를 해야 합니다. 처음에 korat.iptime.org(공유기 기본 DDNS)를 그대로 쓰려 했는데, Let’s Encrypt가 인증서를 발급해주지 않는 겁니다. 이틀 동안 Caddy 로그, 포트포워딩, 방화벽을 다 뒤졌는데 원인을 못 찾다가 우연히 이 명령을 쳐봤습니다:

dig +short CAA iptime.org

결과: 0 issue ";" — 모든 CA의 인증서 발급을 전면 차단하는 레코드가 박혀 있었던 겁니다. iptime.org 상위 도메인 DNS는 제가 제어할 수 없으니 고칠 방법이 없어요. DuckDNS로 갈아타는 수밖에 없었습니다.

DuckDNS는 무료고, CAA 레코드 제한도 없고, 동적 IP 추적도 내장되어 있습니다. 새 도메인을 쓰기 전에 dig +short CAA <도메인> 한 번 치는 습관이 생겼습니다.


왜 작동하나

이 패턴이 깔끔한 이유는 역할 분리가 명확해서입니다.

  • Caddy: 외부 트래픽 진입점, TLS 관리, 도메인 라우팅 전담
  • web_edge: Caddy와 앱 컨테이너들의 내부 통신망
  • 각 앱: 자기 포트만 web_edge 안에서 쓰면 됨, 호스트 포트 노출 불필요

hermes-discord처럼 외부에서 들어오는 트래픽이 없는 앱(Discord가 서버에 연결하는 게 아니라 봇이 Discord에 연결하는 방식)은 web_edge 자체가 필요 없습니다. 아웃바운드 연결만 하면 되니까요. 이런 앱은 그냥 docker compose up -d로 올리면 완전히 독립적으로 돌아갑니다.


다음 편

2편에서는 서버를 외부에 열자마자 로그에 쌓이기 시작한 SSH 브루트포스 시도 이야기, 그리고 3단계 보안 하드닝 플레이북(SSH 키 → 비밀번호 인증 비활성화 → fail2ban, 이 순서가 중요합니다)을 다룹니다.

집에 돌지 않는 컴퓨터 한 대가 있다면, 월 0원 서버가 될 수 있습니다. 이 글의 web_edge 패턴만 따라가면 앱 추가는 docker-compose.yml 수정 한 줄입니다.


다음 편 ->: 집 서버에 주식 앱 배포하다 겪은 삽질 3가지 배경: 헤르메스를 집서버에서 돌리게 된 이유 · 함께 보기: Discord ↔ 위키 루프 봇 구축기 · 주식앱 배포 삽질기 · 한새암 미술관 개발기

집 서버 운영 시리즈 1 / 2
댓글