잘 돌던 빌드가 아침에 갑자기 깨지는 경우가 있다. 로그를 열어 보면 내 코드 문제가 아니라 npmjs 응답 지연이나 Docker Hub pull 제한 같은 외부 사정인 경우가 많다. 예전에 Nexus OSS 설치 글에서 구버전 설치 과정을 다룬 적이 있는데, 지금은 Docker 이미지 하나로 훨씬 간단하게 띄울 수 있어서 연결과 운영까지 묶어 다시 정리한다.
Nexus Repository 3를 사내에 하나 두고 외부 저장소는 Proxy로 캐시하고, 사내 산출물은 Hosted에 올리고, 클라이언트에는 Group URL 하나만 알려 주면 된다. 이렇게 하면 외부 저장소가 잠시 흔들려도 이미 캐시된 의존성으로 빌드를 이어 갈 수 있고, 뒤쪽 저장소 구성을 바꿔도 클라이언트 설정은 그대로 둘 수 있다. 운영에서는 Blob Store 용량, 정리 작업, 백업, 권한 네 가지를 챙기면 된다.
1. 개요
Sonatype Nexus Repository는 아티팩트 저장소 관리자(repository manager)다. 빌드 도구가 인터넷의 공개 저장소에 직접 가는 대신 사내 서버 한 곳을 거치게 만들고, 그 서버가 외부 패키지를 받아 보관하면서 사내에서 만든 라이브러리와 이미지도 함께 관리한다.
지원 포맷은 Maven, npm, Docker/OCI, PyPI, NuGet, apt, yum, Go, Helm, raw 등이다. 언어별로 Artifactory나 Verdaccio, 사설 레지스트리를 따로 두지 않고 한 서버로 묶을 수 있다는 점이 가장 큰 장점이다.
외부 저장소 때문에 빌드가 깨지는 경우는 대개 이런 것들이다.
- npmjs 같은 공개 레지스트리가 느려지거나 일시적으로 응답하지 않는다.
- 의존하던 패키지 버전이 공개 저장소에서 삭제된다.
- CI 러너 여러 대가 같은 IP로 Docker Hub에서 이미지를 받다가 pull 제한에 걸린다.
아래에서는 저장소 종류를 먼저 짚고, Docker로 기동한 뒤 Maven, npm, Docker 클라이언트를 차례로 연결하고, 마지막으로 운영할 때 챙길 항목을 살펴본다.
2. 저장소는 세 종류만 알면 된다
Nexus의 저장소는 역할에 따라 Hosted, Proxy, Group 세 가지로 나뉜다. 이 구분을 이해하면 나머지 설정은 금방 따라온다.
2.1 Hosted
직접 업로드하는 저장소다. 사내 라이브러리나 빌드 산출물을 여기에 올린다. Maven 기본 저장소인 maven-releases와 maven-snapshots가 Hosted이고, 각각 release 버전 정책과 snapshot 버전 정책이 걸려 있다.
2.2 Proxy
외부 저장소의 캐시다. 처음 요청이 들어오면 원격에서 받아 저장하고, 다음부터는 저장해 둔 것을 내준다. 기본 제공되는 maven-central은 https://repo1.maven.org/maven2/를 바라보는 Proxy이고, npm이라면 https://registry.npmjs.org를 바라보는 npm-proxy를 만들어 쓴다.
2.3 Group
여러 저장소를 URL 하나로 묶은 것이다. 기본 제공되는 maven-public은 maven-central, maven-releases, maven-snapshots 세 개를 묶고 있다. npm도 npm-proxy와 사내 Hosted를 묶은 npm-group을 만들어 쓰면 된다.
💡 클라이언트에는 Group URL만 알려 주는 것을 추천한다. 나중에 Proxy를 하나 더 붙이거나 Hosted를 나눠도 Group 구성만 바꾸면 되고, 개발자 PC와 CI 설정은 손댈 필요가 없다. 처음에 저장소별 URL을 각자 박아 넣게 해 두면, 구성을 바꿀 때마다 노가다가 시작된다.
3. Docker로 기동하기
3.1 볼륨을 만들고 컨테이너 실행
데이터는 반드시 /nexus-data에 볼륨을 붙여서 보존한다. 설정, 로그, 저장소 데이터가 모두 이 경로에 쌓이기 때문에 볼륨 없이 띄웠다가 컨테이너를 지우면 전부 사라진다.
$ docker volume create --name nexus-data nexus-data $ docker run -d --name nexus -p 8081:8081 -v nexus-data:/nexus-data sonatype/nexus3
Docker 저장소까지 쓸 계획이라면, 뒤에서 만들 HTTP 커넥터 포트(예: 8082)도 처음부터 열어 두는 것이 편하다. 컨테이너를 다시 만들지 않아도 된다.
$ docker run -d --name nexus -p 8081:8081 -p 8082:8082 \
-v nexus-data:/nexus-data sonatype/nexus3
호스트 디렉터리를 직접 마운트하는 방식도 있다. 이 경우 컨테이너 안의 Nexus 프로세스가 UID 200으로 돌기 때문에 디렉터리 소유자를 200으로 맞춰야 한다. 이걸 빠뜨리면 기동 중에 쓰기 권한 오류로 멈춘다.
$ sudo mkdir -p /data/nexus-data $ sudo chown -R 200 /data/nexus-data $ docker run -d --name nexus -p 8081:8081 -v /data/nexus-data:/nexus-data sonatype/nexus3
JVM 메모리는 INSTALL4J_ADD_VM_PARAMS 환경 변수로 넘긴다. 값은 서버 사양에 맞게 각자 알아서 정한다.
$ docker run -d --name nexus -p 8081:8081 -v nexus-data:/nexus-data \
-e INSTALL4J_ADD_VM_PARAMS="-Xms2703m -Xmx2703m" sonatype/nexus3
3.2 기동 확인과 초기 비밀번호
새 컨테이너는 서비스가 뜨기까지 몇 분 걸린다. Docker Hub 설명에는 2~3분 정도로 안내되어 있다. 막상 띄워 보면 포트는 열렸는데 브라우저에서 접속이 안 되는 시간이 있어서 이때 설치가 잘못된 줄 알고 컨테이너를 지우는 경우가 많은데, 로그를 보며 기다리면 된다.
$ docker logs -f nexus ... Started Sonatype Nexus COMMUNITY 3.x.x-xx ...
위 로그 줄이 보이면 http://서버주소:8081로 접속한다. 버전 번호는 받은 이미지에 따라 다르다. 사용자명은 admin이고 초기 비밀번호는 데이터 디렉터리의 admin.password 파일에 들어 있다.
$ docker exec nexus cat /nexus-data/admin.password
⚠️ 컨테이너를 내릴 때는 DB가 정상 종료할 시간을 줘야 한다. 공식 안내대로 120초 여유를 두고 멈춘다.
$ docker stop --time=120 nexus
4. 초기 마법사와 기본 저장소 확인
4.1 마법사에서 정하는 것
처음 로그인하면 설정 마법사가 뜨고, 여기서 admin 비밀번호를 바꿔야 다음으로 넘어갈 수 있다. 바꾸고 나면 admin.password 파일은 지워진다.
마법사의 다른 단계에서는 익명 접근(anonymous access) 허용 여부를 고른다. 공식 문서에 따르면 이 값을 정하기 전까지는 인증 없이 저장소 내용을 읽을 수 있는 상태다.
어느 쪽을 고르느냐에 따라 이후 클라이언트 설정이 달라진다.
- 익명 허용: 의존성 다운로드는 인증 없이 되고, 배포(업로드)할 때만 계정이 필요하다. 사내망에서만 접근 가능한 서버라면 이쪽이 설정이 단순하다.
- 익명 차단: 다운로드에도 계정이 필요하다. Maven, npm, Docker 모두 읽기 단계부터 자격증명을 넣어야 한다.
인터넷에 노출되는 서버라면 익명 차단을 추천한다. 나중에 바꾸려면 Settings > Security > Anonymous Access에서 조정한다.
4.2 기본 저장소 확인
마법사가 끝나면 Settings > Repository > Repositories에서 저장소 목록을 확인한다. Maven 쪽은 maven-central, maven-releases, maven-snapshots, maven-public이 기본으로 만들어져 있다.
npm은 직접 만들어야 한다. Create repository에서 다음 세 개를 순서대로 만든다.
npm (proxy): 이름npm-proxy, Remote storage에https://registry.npmjs.orgnpm (hosted): 이름npm-hosted, 사내 패키지 배포용npm (group): 이름npm-group, 멤버로npm-hosted와npm-proxy추가
⚠️ npm Proxy의 원격 URL은 한 번 정하면 바꾸지 않는다. 공식 문서에 따르면 바꾸는 순간 캐시된 데이터를 찾지 못해 404가 날 수 있다. 다른 레지스트리를 붙여야 하면 Proxy를 새로 만들어 Group에 추가한다.
4.3 Realm 활성화
Realm은 Nexus가 인증 요청을 어떤 방식으로 처리할지 정하는 모듈이다. npm 로그인과 Docker 클라이언트를 쓰려면 Settings > Security > Realms에서 다음 두 개를 Active 목록으로 옮기고 저장한다.
- npm Bearer Token Realm:
npm adduser(=npm login)로 인증하고 토큰으로 publish할 때 필요하다. - Docker Bearer Token Realm: Docker 클라이언트로 저장소에 접근할 때 필요하다. 익명 pull을 허용할 때도 필요하다.
처음엔 이걸 빼먹고 npm login이나 docker login이 401로 실패하는 이유를 한참 찾게 된다. 저장소를 만든 직후 같이 켜 두는 것을 추천한다.
5. Maven 연결하기
5.1 CI 전용 계정 만들기
배포에 admin 계정을 그대로 쓰지 말고, Settings > Security > Users에서 CI 전용 계정(예: ci-deployer)을 만든다. 권한 설계는 7.3에서 다시 다룬다.
5.2 settings.xml에 미러와 자격증명 넣기
~/.m2/settings.xml에 미러를 걸면 모든 저장소 요청이 Nexus의 Group으로 간다. <mirrorOf>*</mirrorOf>가 "모든 원격 저장소를 이 URL로 대신한다"는 뜻이다.
<settings>
<mirrors>
<mirror>
<id>nexus</id>
<mirrorOf>*</mirrorOf>
<url>http://nexus.example.com:8081/repository/maven-public/</url>
</mirror>
</mirrors>
<servers>
<server>
<id>nexus</id>
<username>ci-deployer</username>
<password>${env.NEXUS_PASSWORD}</password>
</server>
</servers>
</settings>
여기서 <server>의 id가 미러와 배포 저장소의 id와 같아야 자격증명이 붙는다. 익명 차단 환경이라면 이 server 항목 덕분에 다운로드에도 인증이 실린다.
5.3 pom.xml에 배포 위치 지정
배포 대상은 Group이 아니라 Hosted다. Group은 읽기 전용 묶음이라 여기에 올릴 수 없다.
<distributionManagement>
<repository>
<id>nexus</id>
<name>Releases</name>
<url>http://nexus.example.com:8081/repository/maven-releases</url>
</repository>
<snapshotRepository>
<id>nexus</id>
<name>Snapshot</name>
<url>http://nexus.example.com:8081/repository/maven-snapshots</url>
</snapshotRepository>
</distributionManagement>
5.4 배포 확인
$ export NEXUS_PASSWORD='********' $ mvn clean deploy ... Uploading to nexus: http://nexus.example.com:8081/repository/maven-snapshots/com/example/demo/0.0.1-SNAPSHOT/... Uploaded to nexus: http://nexus.example.com:8081/repository/maven-snapshots/com/example/demo/0.0.1-SNAPSHOT/... ... [INFO] BUILD SUCCESS
버전이 -SNAPSHOT으로 끝나면 maven-snapshots로, 아니면 maven-releases로 올라간다. 자주 만나는 실패는 두 가지다.
- 401 Unauthorized:
settings.xml의 serverid와distributionManagement의id가 다른 경우가 자주 보인다. - 400 Bad Request로 release 업로드 실패: 이미 같은 버전이
maven-releases에 있을 때 난다. release 저장소는 같은 버전 재배포를 막아 두는 설정이 걸려 있으니, 버전을 올려서 다시 배포한다.
6. npm 연결하기
6.1 registry를 Group으로 지정
$ npm config set registry http://nexus.example.com:8081/repository/npm-group/ $ npm config get registry http://nexus.example.com:8081/repository/npm-group/
익명 허용 환경이라면 이것만으로 npm install이 Nexus를 거친다. 처음 설치는 Nexus가 npmjs에서 받아 오느라 직접 받을 때와 비슷하거나 조금 느릴 수 있고, 두 번째부터는 캐시에서 나간다.
6.2 인증 설정
방법은 두 가지다. 개발자 PC에서는 로그인 방식이 편하다. npm 9부터 인증 방식이 바뀌어서 --auth-type=legacy를 붙여야 Nexus에 로그인이 된다.
$ npm adduser --auth-type=legacy --registry=http://nexus.example.com:8081/repository/npm-group/
성공하면 .npmrc에 해당 레지스트리용 인증 줄이 자동으로 추가된다. 이 방식은 4.3의 npm Bearer Token Realm이 켜져 있어야 한다.
CI에서는 대화형 로그인을 쓸 수 없으니 .npmrc에 Basic 인증을 직접 넣는다. _auth 값은 사용자:비밀번호를 base64로 인코딩한 것이다.
$ echo -n 'ci-deployer:비밀번호' | base64
registry=http://nexus.example.com:8081/repository/npm-group/ email=ci@example.com //nexus.example.com:8081/repository/npm-group/:_auth=<base64 값>
⚠️ _auth 앞의 URL은 http:를 뺀 //호스트:포트/경로/ 형태여야 하고, registry 값과 경로가 정확히 같아야 한다. 끝의 슬래시 하나가 빠져도 인증이 안 붙어서 401이 난다. 이 파일은 저장소에 커밋하지 말고 CI의 시크릿으로 주입하는 것을 추천한다.
6.3 사내 패키지 배포
publish는 Group이 아니라 Hosted로 보낸다. package.json의 publishConfig로 지정해 두면 실수로 다른 곳에 올라가는 것을 막을 수 있다.
{
"name": "@example/common-utils",
"version": "1.0.0",
"publishConfig": {
"registry": "http://nexus.example.com:8081/repository/npm-hosted/"
}
}
$ npm publish
publish할 때도 npm-hosted 경로에 대한 인증이 필요하다. .npmrc에 //nexus.example.com:8081/repository/npm-hosted/:_auth=... 줄을 하나 더 넣는다.
7. Docker 저장소는 왜 따로 손이 갈까?
Maven과 npm은 /repository/저장소이름/ 경로로 바로 붙지만, Docker 클라이언트는 레지스트리 주소를 호스트[:포트] 단위로 다룬다. 그래서 Docker 저장소에는 경로를 나눠 줄 별도 방법이 필요하고, Docker가 HTTPS를 요구한다는 제약도 함께 풀어야 한다.
7.1 저장소 구성
Maven, npm과 같은 방식으로 세 개를 만든다.
docker (proxy):docker-proxy, Remote storage에https://registry-1.docker.io, Docker Index는 Use Docker Hub 선택docker (hosted):docker-hosted, 사내 이미지 push용docker (group):docker-group, 위 두 개를 멤버로
7.2 경로 구분 방법: 포트 커넥터와 경로 기반 라우팅
포트 커넥터는 저장소마다 HTTP 포트를 하나씩 배정하는 방식이다. 저장소 설정의 Repository Connectors에서 HTTP 포트(예: docker-group에 8082)를 지정하고, 컨테이너에서 그 포트를 열어 둔다. 3장에서 -p 8082:8082를 미리 열어 둔 이유가 이것이다. 공식 문서는 포트 커넥터를 최대 20개까지만 쓰라고 안내한다.
경로 기반 라우팅(path-based routing)은 3.83.0부터 들어온 방식으로, 저장소 이름을 이미지 경로에 넣는다. 포트를 여러 개 열거나 와일드카드 인증서를 관리할 필요가 없어서 공식 문서도 이쪽을 우선 권장한다. 저장소 설정에서 Path-based routing 라디오 버튼을 선택하면 된다.
# 경로 기반 라우팅 (3.83.0 이상) $ docker pull nexus.example.com/docker-group/library/alpine:latest # 포트 커넥터 $ docker pull nexus.example.com:8082/library/alpine:latest
새로 구축한다면 3.83.0 이상 버전에서 경로 기반 라우팅을 쓰는 것을 추천한다. 기존 서버가 포트 커넥터로 돌고 있다면 클라이언트 주소가 바뀌는 문제가 있으니 이전 계획을 따로 세운다.
7.3 HTTPS는 리버스 프록시로
Docker는 레지스트리와 HTTPS로 통신하는 것을 전제로 한다. 데몬의 --insecure-registry 옵션으로 검증을 건너뛸 수는 있지만, Nexus는 이 옵션 사용을 지원하지 않는다고 공식 문서에 명시되어 있다. 처음엔 이걸로 버티다가 문제를 만나는 경우가 많으니 처음부터 TLS를 붙인다.
현장에선 Nginx 같은 리버스 프록시에서 TLS를 끝내고 Nexus로 넘기는 방식이 다루기 쉽다. 포트 커넥터를 쓴다면 아래처럼 8082로 보낸다(경로 기반 라우팅이면 8081로 보낸다).
server {
listen 443 ssl;
server_name docker.example.com;
ssl_certificate /etc/letsencrypt/live/docker.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/docker.example.com/privkey.pem;
# 이미지 레이어는 크기가 크므로 본문 크기 제한을 푼다
client_max_body_size 0;
location / {
proxy_pass http://127.0.0.1:8082;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
client_max_body_size를 빼먹으면 Nginx 기본값(1MB)에 걸려 push 도중 413 Request Entity Too Large가 난다. 작은 레이어는 올라가다가 큰 레이어에서만 실패해서 원인을 찾기 헷갈리는 함정이다.
7.4 로그인과 익명 pull
$ docker login docker.example.com Username: ci-deployer Password: Login Succeeded $ docker tag myapp:1.0.0 docker.example.com/myapp:1.0.0 $ docker push docker.example.com/myapp:1.0.0
자격증명은 ~/.docker/config.json에 저장되고, 저장소(또는 Group)마다 따로 로그인해야 한다. ⚠️ 포트 커넥터 방식에서는 레지스트리 주소에 /repository-name 같은 경로를 붙이면 인증이 실패한다. 호스트와 포트까지만 쓴다.
push 대상은 Hosted여야 한다. Group 주소로 push하는 것은 Community Edition에서 지원되지 않으니, push용 주소(docker-hosted)와 pull용 주소(docker-group)를 나눠서 안내하는 것을 추천한다.
로그인 없이 pull을 허용하려면 세 가지가 모두 켜져 있어야 한다.
- Docker Bearer Token Realm 활성화 (4.3)
- Settings > Security > Anonymous Access에서 "Allow anonymous users to access the server" 체크
- 해당 Docker 저장소의 Repository Connectors에서 "Allow anonymous Docker pulls for this repository" 체크
8. 운영할 때 챙길 것
8.1 Blob Store 용량을 지켜보자
Blob Store는 Nexus가 실제 파일(아티팩트 바이너리)을 저장하는 공간이다. Proxy는 요청받은 것을 계속 쌓고, snapshot과 이미지는 빌드마다 늘어나기 때문에 디스크는 생각보다 빨리 찬다. Blob Store가 있는 디스크에 용량 알람을 걸어 두는 것을 추천한다. Docker 볼륨을 썼다면 볼륨이 있는 호스트 디스크를 본다.
$ docker system df -v | grep nexus-data $ df -h /var/lib/docker
8.2 Cleanup Policy만으로는 공간이 줄지 않는다
Cleanup Policy는 오래된 snapshot이나 일정 기간 내려받지 않은 컴포넌트를 정리 대상으로 지정하는 규칙이다. Settings > Repository > Cleanup Policies에서 만들고 각 저장소 설정에 연결한다.
여기서 많이들 막힌다. 정리 작업은 컴포넌트를 소프트 삭제만 한다. 메타데이터에 삭제 표시를 할 뿐이라 디스크 사용량은 그대로다. 실제 공간은 Admin - Compact blob store 태스크가 돌아야 회수된다.
공식 문서가 안내하는 순서는 이렇다.
- Admin - Cleanup repositories using their associated policies
- Admin - Cleanup unused asset blobs
- Admin - Compact blob store
Settings > System > Tasks에서 이 태스크들이 등록되어 있고 실제로 돌고 있는지 확인한다. Compact 태스크의 Blobs Older Than 값을 주면 삭제된 블롭을 지정한 일수만큼 남겨 두었다가 지운다. 실수로 지운 것을 되살릴 여유를 두고 싶을 때 쓴다.
8.3 권한은 Role과 Content Selector로 나눈다
Nexus 권한은 "Content Selector > Privilege > Role > 사용자" 순서로 쌓인다. Content Selector는 저장소 안의 특정 경로만 골라내는 조건식(CSEL)이다.
format == "maven2" and path =^ "/com/example/payment/"
위 조건으로 Content Selector를 만들고, 이를 repository-content-selector 타입 Privilege에 연결한 뒤, 그 Privilege를 팀 Role에 넣는다. 이렇게 하면 결제팀 계정은 자기 group ID 아래에만 배포할 수 있다.
공식 문서는 Content Selector를 주로 팀별 쓰기 권한에 쓰고, 읽기를 막아야 하는 것은 아예 별도 저장소로 분리하라고 권한다. 조건식은 ==와 =^가 빠르고, 정규식 =~는 느리다.
CI 계정은 사람 계정과 분리한다. 배포할 Hosted에 쓰기 권한, Group에 읽기 권한만 준 전용 Role을 만들어 붙인다. 퇴사자 계정을 정리하다가 CI가 같이 멈추는 일을 막을 수 있다.
8.4 백업은 DB와 Blob Store를 함께
Nexus는 파일은 Blob Store에, 메타데이터와 설정은 DB에 따로 둔다. 둘을 거의 같은 시점에 함께 백업해야 복원했을 때 서로 어긋나지 않는다. 한쪽만 백업해 두면 복원 후 "DB에는 있는데 파일이 없는" 상태가 생긴다.
- H2 DB 사용 시: Admin - Backup H2 Database 태스크로 DB를 zip으로 내보내고, Blob Store는 따로 복사한다.
- PostgreSQL 사용 시: PostgreSQL 자체 백업 도구(또는 클라우드 백업 기능)를 쓰고, Blob Store 백업 시점을 맞춘다. 공식 문서는 운영 환경에 PostgreSQL을 권장한다.
백업은 복원을 한 번 해 봐야 의미가 있다. 별도 서버에 새 컨테이너를 띄워 복원해 보는 것을 추천한다.
8.5 버전과 라이선스는 어떻게 고를까?
Nexus 2는 2025년 6월 30일로 지원이 끝났다. 이후로는 기능 추가도 버그 수정도 없다. 아직 2.x를 쓰고 있다면 3 계열로 옮겨야 하고, 새로 깐다면 당연히 3 계열이다.
무료 판인 Community Edition에는 사용량 제한이 있다. 작성 시점 공식 문서 기준으로 전체 컴포넌트 40,000개 또는 하루 요청 100,000건이고, 이를 넘으면 다시 기준 아래로 내려갈 때까지 새 컴포넌트를 추가할 수 없다. 조건은 바뀔 수 있으니 도입 전에 공식 문서의 Usage Center 항목에서 현재 조건을 확인하고, 운영 중에는 Usage Center 화면으로 사용량을 지켜본다. Proxy가 쌓는 캐시도 컴포넌트로 잡히므로 Cleanup Policy가 여기서도 중요하다.
8.6 대안은 무엇이 있을까?
- JFrog Artifactory: Nexus와 같은 범용 아티팩트 저장소다. 여러 포맷을 한곳에 모은다는 목적은 같으니 기존 사내 표준이나 라이선스 조건을 보고 고른다.
- Harbor: 컨테이너 이미지 전용 레지스트리다. Docker/OCI 이미지만 관리하면 된다면 후보가 되지만, Maven과 npm까지 묶으려면 별도 저장소가 필요하다.
Maven, npm, Docker를 함께 쓰는 팀이라면 Nexus 하나로 시작하는 것을 추천한다. 컨테이너 이미지만 다룬다면 Harbor도 충분하다.
9. 정리
- 외부 저장소는 Proxy로 캐시하고, 사내 산출물은 Hosted에 올리고, 클라이언트에는 Group URL 하나만 준다.
- Docker로 띄울 때는
/nexus-data볼륨이 필수이고, 첫 기동은 몇 분 기다린다. - npm과 Docker는 Realm 활성화가 먼저다. Docker는 HTTPS와 경로 구분 방법(경로 기반 라우팅 또는 포트 커넥터)까지 챙긴다.
- 운영에서는 Cleanup 뒤 Compact로 공간을 회수하고, DB와 Blob Store를 함께 백업하고, CI 전용 계정으로 권한을 나눈다.