내 Synology NAS에는 GitLab, WordPress, 자동화 도구와 개인 프로젝트가 Docker 컨테이너로 함께 돌아간다. 2026년 8월에 Container Manager를 열어 보니 정상 컨테이너 21개와 이미지 26개가 잡혀 있었다. 서비스를 올리는 것보다 더 자주 헷갈린 것은 그다음이었다. 컨테이너 포트를 어디까지 열고, 도메인과 DSM 역방향 프록시, HTTPS 인증서를 어떤 순서로 연결해야 하는가.
처음에는 컨테이너의 호스트 포트로 바로 접속하면 충분해 보였다. 서비스가 늘어나자 포트를 기억하기 어려웠고, 각 애플리케이션이 인터넷에 직접 노출되는 범위도 넓어졌다. 지금은 외부 사용자가 HTTPS 443으로만 들어오고, DSM이 요청을 NAS 내부의 loopback 포트로 넘기는 구조를 기본으로 삼고 있다. 이 글은 실제 DSM 7.2.2와 Container Manager 화면을 확인하면서 그 전체 과정을 한 번에 정리한 기록이다.
본문의 DSM 화면은 내 NAS에서 직접 캡처했다. 다만 운영 도메인, 내부 IP, 포트, 컨테이너 이름과 인증서 식별 정보는
example.com기반 예시 값으로 바꿨다. 캡처 과정에서 설정을 저장하거나 변경하지 않았다.
1. 내가 정착한 요청 흐름
브라우저에서 컨테이너까지 가는 경로는 다섯 단계로 나눠 보면 단순해진다.

- 사용자는
https://service.example.com으로 접속한다. - DNS의 A 또는 AAAA 레코드가 NAS가 있는 네트워크를 가리킨다.
- 공유기나 방화벽이 TCP 443을 NAS로 전달한다.
- DSM 역방향 프록시가 호스트 이름을 보고
http://localhost:18080으로 넘긴다. - Docker 포트 매핑이 그 요청을 컨테이너의 애플리케이션 포트로 전달한다.
여기서 내가 지키는 원칙은 애플리케이션 포트를 외부에 직접 공개하지 않는 것이다. 공유기에는 443만 열고, Docker 포트는 가능하면 127.0.0.1에 바인딩한다. DSM 역방향 프록시와 컨테이너가 같은 NAS에 있기 때문에 이 경로로 충분하다.
2. 실제 Container Manager 상태부터 확인했다
설정 방법을 쓰기 전에 현재 NAS가 어떻게 운영되고 있는지부터 확인했다. 캡처 당시 Container Manager에는 정상 컨테이너 21개, 이미지 26개가 표시됐다. CPU와 메모리는 여유가 있었지만 이 숫자는 고정된 성능 기준이 아니다. 어떤 이미지와 데이터베이스를 돌리는지, 메모리 제한을 걸었는지, 디스크 I/O가 얼마나 발생하는지에 따라 같은 개수라도 부담은 달라진다.

의외였던 부분은 프로젝트 목록이 비어 있었다는 점이다. 컨테이너는 많이 실행 중이지만 Container Manager의 프로젝트 기능으로 등록된 Compose 프로젝트는 없었다. 오래 운영하면서 개별 컨테이너와 명령줄 배포가 쌓인 결과였다. 실행 중이라는 사실과 재현 가능한 운영 구성이 있다는 사실은 달랐다.

이 상태에서 모든 기존 컨테이너를 한꺼번에 옮기지는 않는다. 먼저 새 서비스부터 Compose 프로젝트로 만들고, 기존 서비스는 백업과 복구 경로를 확인한 뒤 하나씩 전환하는 편이 안전하다.
3. 시작 전에 네트워크 조건을 구분한다
Compose 파일보다 먼저 확인해야 할 것이 있다. 인터넷에서 NAS까지 요청이 도달할 수 있는 환경인지다.
| 확인 항목 | 왜 필요한가 | 확인 결과에 따른 선택 |
|---|---|---|
| 공인 IPv4 | DNS A 레코드와 포트 전달에 필요하다. | 유동 IP라면 DDNS 갱신을 함께 구성한다. |
| CGNAT 여부 | 통신사 공유 주소라면 일반 포트 포워딩이 동작하지 않는다. | 공인 IP 신청, 터널 또는 내부망 전용 운영을 검토한다. |
| IPv6 | AAAA 레코드는 공유기 NAT를 거치지 않을 수 있다. | NAS와 방화벽의 IPv6 규칙을 별도로 확인한다. |
| TCP 443 전달 | 외부 HTTPS 요청이 DSM 역방향 프록시에 도달해야 한다. | 공유기에서 443을 NAS로 전달한다. |
| 인증서 발급용 80 | DSM의 Let’s Encrypt HTTP 검증과 갱신에 필요할 수 있다. | 발급 방식과 갱신 조건을 확인하고 필요한 기간에만 연다. |
| Cloudflare 프록시 | DNS만 사용하는지 프록시까지 사용하는지에 따라 TLS와 원본 접근 경로가 달라진다. | 처음에는 DNS-only로 검증한 뒤 프록시를 추가하는 편이 원인 분리에 쉽다. |
내부망에서만 사용할 서비스라면 외부 443과 공인 DNS가 꼭 필요한 것은 아니다. 내부 DNS와 사설 인증서, VPN을 쓸 수도 있다. 이 글은 인터넷에서 공개 도메인으로 접속하는 경우를 기준으로 한다.
4. 서비스 폴더와 Compose 파일을 먼저 남긴다
서비스 파일은 한 디렉터리 아래에서 역할별로 나눈다. 실제 볼륨 이름은 NAS 구성에 맞게 바꿔야 한다.
/volume1/docker/my-service/
├── compose.yaml
├── .env
├── .env.example
├── config/
├── data/
├── logs/
└── backup/
compose.yaml과 .env.example은 재현을 위한 파일이고, data와 config는 백업 대상이다. 실제 비밀번호와 토큰이 들어 있는 .env는 블로그나 Git 저장소에 올리지 않는다.
다음은 Nginx를 예로 든 최소 구성이다. 내가 설명하려는 지점은 이미지 자체가 아니라 포트의 왼쪽에 붙인 127.0.0.1이다.
services:
app:
image: nginx:1.27-alpine
container_name: my-service
restart: unless-stopped
ports:
- "127.0.0.1:18080:80"
volumes:
- ./html:/usr/share/nginx/html:ro
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"
"18080:80"이라고만 쓰면 Docker가 기본적으로 호스트의 여러 인터페이스에 포트를 공개할 수 있다. "127.0.0.1:18080:80"은 NAS 자기 자신에서만 18080 포트에 접근하도록 범위를 줄인다. DSM 역방향 프록시는 같은 장비에서 localhost:18080으로 접근할 수 있지만, 외부 사용자는 18080으로 직접 들어오지 못한다.
애플리케이션이 데이터베이스와 함께 움직인다면 데이터베이스에는 ports를 만들지 않고 Compose 내부 네트워크에서 서비스 이름으로 연결한다. 이미지 태그도 latest 대신 검증한 버전을 고정해 두면 업데이트와 롤백이 쉬워진다.
5. Container Manager에서 프로젝트를 만든다
DSM에서 Container Manager → 프로젝트 → 생성으로 들어가면 프로젝트 이름과 경로를 지정하고 docker-compose.yml을 업로드할 수 있다. 화면에서는 파일 이름을 docker-compose.yml로 안내하지만, 로컬에서 관리하는 파일 이름과 형식은 운영 절차에 맞춰 통일하면 된다.

처음 배포할 때는 다음 순서로 확인한다.
- Compose 파일이 해석되는지 확인한다.
- 컨테이너가 반복 재시작하지 않는지 본다.
- NAS 내부에서
curl http://127.0.0.1:18080이 응답하는지 확인한다. - 데이터와 설정 파일이 의도한 폴더에 생겼는지 확인한다.
- 로그에 비밀번호, 토큰 또는 개인 데이터가 출력되지 않는지 본다.
세 번째 확인이 실패하면 DNS나 인증서를 만질 단계가 아니다. 먼저 컨테이너, 포트 매핑과 애플리케이션 로그부터 해결해야 한다.
6. DNS와 공유기에서 443을 NAS까지 연결한다
도메인 제공업체에서 service.example.com의 A 레코드를 공인 IPv4로 연결한다. IPv6를 쓴다면 AAAA 레코드도 추가할 수 있지만, 방화벽 정책까지 함께 확인해야 한다. 유동 공인 IP라면 주소가 바뀔 때 DNS도 갱신돼야 한다.
공유기에서는 외부 TCP 443을 NAS가 있는 내부 주소의 443으로 전달한다. 컨테이너의 18080은 공유기에서 열지 않는다. 인증서 발급 방식이 HTTP 검증이라면 TCP 80도 필요할 수 있다. Synology의 DSM 인증서 도움말은 Let’s Encrypt의 도메인 검증과 자동 갱신을 위해 인터넷에서 포트 80에 접근할 수 있어야 한다고 설명한다.
이 단계에서는 외부 네트워크에서 도메인이 올바른 공인 주소로 해석되는지 확인한다.
nslookup service.example.com
# 또는
dig +short service.example.com
7. DSM 역방향 프록시 규칙을 연결한다
DSM 7에서는 제어판 → 로그인 포털 → 고급 → 역방향 프록시로 이동한다. 규칙의 일반 탭에는 다음 값을 넣는다.
| 구분 | 항목 | 예시 |
|---|---|---|
| 규칙 | 이름 | service.example.com |
| 소스 | 프로토콜 | HTTPS |
| 소스 | 호스트 이름 | service.example.com |
| 소스 | 포트 | 443 |
| 대상 | 프로토콜 | HTTP |
| 대상 | 호스트 이름 | localhost |
| 대상 | 포트 | 18080 |

웹소켓을 사용하는 서비스라면 사용자 지정 머리글에서 WebSocket 관련 항목도 확인한다. 애플리케이션이 원래 요청의 호스트, 프로토콜과 클라이언트 IP를 사용한다면 Host, X-Forwarded-Proto, X-Forwarded-For 처리도 애플리케이션과 함께 검증해야 한다.
HSTS는 브라우저에 HTTPS만 사용하도록 기억시키는 설정이다. 인증서와 HTTPS 접속이 정상임을 먼저 확인한 뒤 켠다. 잘못된 도메인이나 인증서 상태에서 먼저 활성화하면 브라우저가 HTTP 우회를 허용하지 않아 복구가 더 번거로울 수 있다.
8. Let’s Encrypt 인증서를 발급하고 서비스에 할당한다
인증서는 제어판 → 보안 → 인증서에서 관리한다. Synology의 공식 인증서 도움말에 따라 새 인증서를 추가하고 Let’s Encrypt 발급 방식을 선택한 뒤 도메인과 이메일 주소를 입력한다. 발급에 성공했다는 사실만으로 끝나지는 않는다.

인증서 화면의 설정을 눌러 역방향 프록시 서비스와 인증서를 연결해야 한다. 새 인증서가 목록에 있어도 해당 서비스에 다른 인증서가 할당돼 있으면 브라우저에서는 여전히 잘못된 인증서가 보인다.

9. 내부에서 바깥쪽으로 한 단계씩 검증한다
한 번에 브라우저만 열어 보면 오류가 어디에 있는지 구분하기 어렵다. 나는 컨테이너에서 외부 HTTPS 방향으로 한 단계씩 확인한다.
# 1. 컨테이너 상태
docker compose ps
# 2. 최근 로그
docker compose logs --tail 100
# 3. NAS 내부 loopback 포트
curl -I http://127.0.0.1:18080
# 4. 외부 도메인의 HTTPS
curl -I https://service.example.com
# 5. 인증서 체인과 호스트 이름
openssl s_client \
-connect service.example.com:443 \
-servername service.example.com \
</dev/null
| 증상 | 먼저 볼 곳 | 가능성 |
|---|---|---|
| 내부 18080도 응답 없음 | 컨테이너 상태·로그·포트 매핑 | 애플리케이션 미기동, 잘못된 컨테이너 포트 |
| 내부는 정상, 외부는 502 | 역방향 프록시 대상 | 호스트·포트·프로토콜 불일치 |
| 도메인이 다른 주소를 가리킴 | DNS A/AAAA | 레코드 오류, DDNS 갱신 지연 |
| 연결 시간 초과 | 공유기·방화벽·CGNAT | 443 미전달, 통신사 NAT |
| 인증서 이름 불일치 | 인증서 SAN·서비스 할당 | 다른 인증서가 역방향 프록시에 배정됨 |
| 로그에 모든 요청이 프록시 IP로 보임 | 전달 헤더와 신뢰 프록시 설정 | X-Forwarded-For 미처리 |
10. 업데이트보다 먼저 백업과 롤백을 적는다
컨테이너 업데이트는 새 이미지를 받는 명령 한 줄로 끝나 보이지만, 실제 위험은 데이터 형식과 설정 호환성에 있다. 업데이트 전에 다음을 남긴다.
- 현재
compose.yaml과 이미지 태그 .env의 안전한 별도 백업과.env.exampleconfig,data, 데이터베이스 덤프- DSM 역방향 프록시 규칙과 인증서 서비스 할당
- 업데이트 후 확인할 URL, 로그인, 쓰기·읽기 기능
- 문제가 생겼을 때 되돌릴 이전 이미지 태그와 복원 순서
NAS 스냅샷이 있더라도 그것만으로 복구가 끝난다고 생각하지 않는다. 데이터베이스가 일관된 시점에 백업됐는지, 다른 장비나 별도 볼륨에 사본이 있는지, 실제로 복원할 수 있는지를 함께 확인해야 한다.
11. NAS 한 대에 모으는 데에는 대가가 있다
NAS를 서비스 호스트로 쓰면 저장소와 Docker를 한 화면에서 관리할 수 있고, 작은 개인 서비스에는 비용과 관리 부담을 줄여 준다. 반대로 한 장비에 서비스가 모이면 실패 범위도 커진다.
- NAS가 꺼지면 여러 서비스가 함께 멈춘다.
- 데이터 저장소와 서비스 실행 장비가 같을 수 있다.
- Docker 소켓이나 컨테이너 관리 권한은 사실상 강한 시스템 권한이다.
- 일반 서버보다 CPU와 메모리, 냉각 여유가 제한적이다.
- 로그와 데이터베이스 쓰기가 같은 볼륨과 캐시에 집중될 수 있다.
실제로 내 NAS는 여러 서비스의 I/O가 같은 볼륨과 NVMe 캐시를 거치는 상태에서 캐시 SSD가 70°C에 도달해 보호 종료된 적이 있다. Docker 서비스가 직접 원인이었다고 단정할 수는 없지만, 한 장비에 부하와 실패가 모이는 구조는 분명히 확인했다. 당시 로그와 점검 과정은 Synology NAS가 꺼졌다. 70°C NVMe 캐시가 원인이었다에 따로 기록했다.
그래서 지금은 “접속되면 끝”보다 다음 상태를 남기는 데 더 신경 쓴다. Compose 파일로 다시 만들 수 있는가. 데이터는 별도로 백업됐는가. 외부에는 443만 열려 있는가. 인증서가 실제 서비스에 할당됐는가. 문제가 생기면 이전 이미지와 데이터로 돌아갈 수 있는가.
12. 전체 순서를 다시 따라가 보니
이 구성을 순서로 줄이면 다음과 같다.
- 서비스 폴더, Compose 파일과 백업 경계를 먼저 정한다.
- Docker 포트를
127.0.0.1에 바인딩한다. - NAS 내부에서 loopback 포트 응답을 확인한다.
- DNS와 공유기의 443 경로를 연결한다.
- DSM 역방향 프록시에서 도메인과 내부 포트를 연결한다.
- Let’s Encrypt 인증서를 발급하고 해당 서비스에 할당한다.
- 외부 네트워크에서 DNS, HTTPS, 인증서와 애플리케이션 기능을 검증한다.
- 업데이트 전에 이미지 태그, 데이터 백업과 롤백 절차를 남긴다.
내 NAS에서는 컨테이너 수에 비해 Container Manager 프로젝트가 비어 있다는 사실도 이번에 처음 분명하게 봤다. 당장 모두 옮기기보다 새 서비스부터 이 구조로 만들고, 기존 서비스는 복구 가능성을 확인하면서 하나씩 정리하려고 한다. 실행 중인 컨테이너를 재현 가능한 서비스로 바꾸는 일은 배포보다 느리지만, NAS를 오래 운영하려면 그 시간이 필요했다.
Container Manager 자체의 역할과 지원 모델은 Synology의 Container Manager 공식 패키지 페이지에서도 확인할 수 있다.


답글 남기기