Docker –network none 로 오프라인 패키지 설치 검증하기
검증이전!!!!
배포용 패키지(.deb, 커스텀 설치 스크립트 등)를 만들 때 가장 자주 놓치는 실수는
"개발 PC에는 이미 깔려 있어서 문제가 안 보이는 의존성" 입니다.
개발자 로컬 환경에는 이런저런 라이브러리가 이것저것 깔려 있다 보니, 정작 오프라인
설치 번들에는 빠져 있는 의존성이 있어도 개발 PC에서는 설치가 잘 됩니다. 그런데
막상 폐쇄망(에어갭) 환경의 실제 대상 장비에 가져가면 apt가 인터넷에 접속하려다
실패하면서 설치가 중간에 멈추죠.
이 글은 이런 문제를 배포 전에 잡아내기 위해, Docker 컨테이너에 네트워크 인터페이스
자체를 제거한 상태로 설치 번들 하나만 반입해서 정말로 자급자족이 되는지 검증하는
절차를 정리한 것입니다. 특정 패키지 이름 없이 어떤 프로젝트에도 적용할 수 있게
일반화했습니다.
왜 –network none 인가
--network none은 컨테이너에 네트워크 인터페이스 자체를 만들지 않습니다.
방화벽 규칙으로 막는 것과 달리 "연결 시도 자체가 물리적으로 불가능한" 상태라
검증 결과를 신뢰할 수 있습니다.- 이렇게 만든 컨테이너 안에서 설치 스크립트를 돌렸을 때
apt-get이
Could not connect to...같은 메시지를 낸다면, 그건 오프라인 번들에
빠진 의존성이 있다는 확실한 증거입니다.
왜 베이스 이미지를 그냥 ubuntu:XX.04로 하면 안 되는가
베이스 이미지를 순정 상태로 두면 검증 자체가 의미 없어집니다. 두 가지를 신경 써야 합니다.
- 베이스 이미지는 실제 배포 대상 호스트의 "최소 상태"를 흉내내야 한다.
개발/빌드 전용 패키지(build-essential,cmake등)는 베이스 이미지에
넣지 마세요. 실제 배포 대상 호스트에는 없을 것들이라, 넣으면 대상 호스트보다
관대한 환경이 되어 검증이 느슨해집니다. - 설치 대상 패키지가 스스로 설치할 의존성은 베이스 이미지에 미리 깔지 말 것.
미리 깔아두면 "번들이 정말 이걸 가져오는지"를 확인할 수 없어 테스트가 무의미해집니다.
즉 베이스 이미지에 넣을 목록은 아래아 같습니다.
설치
- (실제 배포 대상 호스트에 이미 존재하는 패키지)
제외
− (개발/빌드 전용 도구)
− (설치 대상 패키지 자신의 직접 의존성)
이 목록을 확실히 하는 가장 좋은 방법은 실기기(또는 실기기와 동일한 이미지)에서
apt-get install --simulate <패키지명>을 돌려 어떤 패키지가 이미 만족되어 있는지
실측하는 것입니다. 감으로 목록을 짜지 마세요.
1. Dockerfile 템플릿
<베이스-런타임-패키지들> 자리를 위 방법으로 실측한 목록으로 채워 넣으세요.
서비스 데몬(systemd 유닛)까지 기동 검증하려면 systemd를 함께 넣습니다.
FROM ubuntu:26.04
ENV DEBIAN_FRONTEND=noninteractive
# 대상 호스트 베이스 + systemd(서비스 기동 검증용)
RUN apt-get update && apt-get install -y --no-install-recommends \
systemd systemd-sysv \
<베이스-런타임-패키지들> \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# 설치 스크립트가 기존 계정 존재를 전제로 한다면 여기서 미리 만들어 둡니다.
# (패키지가 직접 만드는 계정이라면 이 줄은 생략)
# RUN groupadd -g 1000 <계정명> && useradd -m -u 1000 -g 1000 -s /bin/bash <계정명>
STOPSIGNAL SIGRTMIN+3
CMD ["/sbin/init"]
빌드는 온라인 상태에서 1회만 하면 됩니다.
sudo docker build -t pkg-verify-base -f Dockerfile.verify .
2. Phase A — 파일 배치 + 오프라인 의존성 해결 검증
설치 스크립트가 systemd 유무와 무관하게 실행 가능하다면(즉, systemctl 실패를
무시하도록 작성돼 있다면), 서비스 기동까지 볼 필요 없는 이 단계는 일반 컨테이너로
가볍게 먼저 확인할 수 있습니다.
# 네트워크 인터페이스 자체가 없어 아웃바운드가 물리적으로 불가능
sudo docker run -d --name verify-a --network none pkg-verify-base sleep infinity
# 오프라인 반입을 흉내: 빌드 트리나 apt 캐시는 옮기지 않고 설치 파일만 옮김
sudo docker cp ./my-package-offline-install.run verify-a:/root/
# 네트워크 접속을 시도하는지 지켜보며 실행
sudo docker exec verify-a /root/my-package-offline-install.run
설치 후 확인 항목 예시:
sudo docker exec verify-a test -x /usr/bin/my-binary && echo "OK: 바이너리"
sudo docker exec verify-a test -f /usr/lib/systemd/system/my-service.service && echo "OK: 유닛"
sudo docker exec verify-a test -f /etc/my-app/config.env && echo "OK: 설정 파일"
3. Phase B — 서비스 기동 검증 (systemd 컨테이너)
파일 배치까지는 문제없었어도, 실제로 서비스가 active (running) 상태까지
올라가는지는 systemd가 PID 1로 떠 있는 컨테이너에서 별도로 확인해야 합니다.
sudo docker run -d --name verify-b --network none --privileged \
--tmpfs /run --tmpfs /run/lock \
-v /sys/fs/cgroup:/sys/fs/cgroup:rw \
pkg-verify-base
# systemd가 정상 기동했는지 확인 (몇 초 걸릴 수 있음)
sudo docker exec verify-b systemctl is-system-running --wait || true
호스트의 cgroup 구성에 따라 systemd가 뜨지 않으면(
docker logs verify-b에
cgroup 관련 에러)--cgroupns=host플래그를 추가해 재시도하세요.
sudo docker cp ./my-package-offline-install.run verify-b:/root/
sudo docker exec verify-b /root/my-package-offline-install.run
sudo docker exec verify-b systemctl enable --now my-service.service
sudo docker exec verify-b systemctl status my-service.service --no-pager
active (running)이 아니라면 로그부터 봅니다.
sudo docker exec verify-b journalctl -u my-service.service --no-pager | tail -30
이 단계에서 자주 나오는 실패 원인은 대부분 다음 둘 중 하나입니다.
- 유닛 파일의
User=가 실제로 존재하지 않는 계정을 가리키는 경우
(Failed to determine user credentials: No such process) - 실행 파일이 오프라인 번들에 없는 라이브러리를 동적 링크하고 있는 경우
(error while loading shared libraries)
참고 — 애플리케이션 자체의 네트워크 통신까지 검증하려면:
--network none컨테이너는 호스트와도 통신할 수 없으므로, 서비스가 외부
API나 서버와 실제로 데이터를 주고받는지까지 확인하려면 이 단계만 별도로
네트워크를 붙인 컨테이너로 분리해서 진행해야 합니다. 이 문서의 범위는
"설치 자체가 네트워크 없이 완결되는가"이지 "런타임 통신이 되는가"가 아닙니다.
4. 제거(remove/purge) 검증
제거는 네트워크가 필요 없는 로컬 동작이므로 --network none 상태 그대로
진행합니다.
sudo docker exec verify-b apt remove -y my-package
sudo docker exec verify-b systemctl is-active my-service.service || echo "정지됨(기대값)"
sudo docker exec verify-b test -f /etc/my-app/config.env && echo "OK: 설정 보존됨(remove는 삭제 안 함)"
sudo docker exec verify-b apt purge -y my-package
sudo docker exec verify-b test -d /etc/my-app && echo "FAIL: 삭제 안 됨" || echo "OK: purge로 삭제됨"
패키지가 만들지 않은 기존 계정이 있었다면, purge 후에도 그 계정은 남아있는 게
정상입니다(패키지 책임 범위 밖이므로).
5. 정리
sudo docker rm -f verify-a verify-b
체크리스트
| 확인 항목 | 절 |
|---|---|
| 검증 환경이 실제 대상 호스트를 흉내내는지 (개발용/설치대상 자체 의존성 제외) | 베이스 이미지 구성 |
| 설치 파일만 반입해 네트워크 접속 시도 없이 설치되는지 | Phase A |
| 설치 후 파일 배치가 정상인지 | Phase A |
| systemd 기준 서비스가 실제로 기동되는지 | Phase B |
| remove/purge 후 상태가 기대대로인지 | 제거 검증 |
한 줄 요약
오프라인 설치 번들을 "완전하다"고 주장하려면, 개발 PC가 아니라 대상 호스트를
흉내낸 깨끗한 환경 + 물리적으로 네트워크가 없는 컨테이너에서 설치 파일 하나만으로
끝까지 돌아가는지 눈으로 확인하는 게 가장 확실합니다. 감으로 의존성 목록을
추정하지 말고, 실기기에서 apt-get install --simulate로 실측한 값을 근거로
삼으세요.