- Published on
폐쇄망 반입을 위해 밖에서 받기 — dnf download, reposync, yumdownloader는 서로 다른 문제를 풉니다
- Authors

- Name
- Youngju Kim
- @fjvbn20031
- 들어가며 — 세 도구는 각각 다른 문제를 풉니다
- dnf download — 필요한 것만 골라 받기
- --alldeps만으로는 부족합니다
- dnf reposync — 저장소를 통째로 복제하기
- yumdownloader는 언제 쓰나
- 받은 것이 완전한지 확인하기
- RHEL 10과 dnf5에서 달라지는 것
- 마치며 — 빈 루트에서 풀고, 반입 전에 닫힘을 확인합니다
- 직접 해보기
- 이전 / 다음 편
- 참고 자료
들어가며 — 세 도구는 각각 다른 문제를 풉니다
1편에서 폐쇄망 설치는 "계산할 수 있는 쪽에서 계산을 끝내는 문제"라고 정리했습니다. 이번 편은 그 계산을 실제로 끝내는 방법입니다.
도구가 셋 있고 이름이 비슷해서 자주 혼동되지만, 셋은 서로 다른 질문에 답합니다.
dnf download— "이 패키지와 그게 필요로 하는 것들을 파일로 주세요"dnf reposync— "이 저장소 전체를 로컬에 복제해 주세요"yumdownloader— RHEL 7 시절부터 쓰던 이름. RHEL 8 이후에는 호환 패키지로 제공됩니다
무엇을 고를지는 규모가 아니라 앞으로 무엇을 깔지 알고 있느냐로 갈립니다.
dnf download — 필요한 것만 골라 받기
가장 단순한 형태부터 보겠습니다.
# 패키지 하나만 현재 디렉터리에 내려받는다 (의존성은 따라오지 않음)
dnf download httpd
# 의존성까지 함께 해결해서 받는다
dnf download --resolve httpd
# 이미 설치된 것도 건너뛰지 않고 전부 받는다
dnf download --resolve --alldeps --destdir ./bundle httpd
공식 dnf-plugins-core 문서는 두 옵션을 이렇게 정의합니다. --resolve는 "Resolves dependencies of specified packages and downloads missing dependencies in the system", --alldeps는 "When used with --resolve, download all dependencies (do not skip already installed ones)"입니다.
두 문장의 차이를 놓치면 안 됩니다. --resolve만 쓰면 이 장비에 없는 것만 받습니다. 폐쇄망에 들고 갈 번들을 만드는 목적이라면 이건 거의 항상 틀린 결과입니다. 받는 장비와 설치할 장비가 다른 시스템이기 때문입니다.
--destdir는 dnf 전역 옵션이고 "Redirect downloaded packages to provided directory"로 문서화되어 있습니다. download 플러그인 문서에는 --downloaddir도 나오는데, 예제에서는 --destdir를 씁니다. 둘 다 같은 전역 옵션의 표기입니다.
--alldeps만으로는 부족합니다
--alldeps를 붙였는데도 폐쇄망에서 막히는 경우가 있습니다. 이유는 이렇습니다.
--alldeps는 "이미 설치된 것을 건너뛰지 말라"고 지시하지만, 의존성 해결 자체는 여전히 이 시스템의 상태를 전제로 이루어집니다. 연결망 장비가 RHEL 9.4이고 설치 대상이 RHEL 9.2라면 해결 결과가 달라질 수 있고, 연결망 장비에 켜져 있는 저장소가 폐쇄망 쪽과 다르면 또 달라집니다.
확실한 방법은 아무것도 설치되지 않은 빈 루트에서 계산하는 것입니다. dnf 공식 문서는 --installroot를 이렇게 설명합니다. "Think of this like doing chroot <root> dnf, except using --installroot allows dnf to work before the chroot is created. It requires absolute path."
그리고 바로 다음 문단에 반드시 읽어야 할 경고가 있습니다.
Note: You may also want to use the command-line option --releasever=<release>
when creating the installroot, otherwise the $releasever value is taken from
the rpmdb within the installroot (and thus it is empty at the time of creation
and the transaction will fail).
빈 루트에는 rpmdb가 없고, 릴리스 버전은 rpmdb에서 유도되므로, 명시하지 않으면 값이 비어서 트랜잭션이 실패한다는 뜻입니다. 실제 명령은 이렇게 됩니다.
# 반입용 빈 루트를 만든다
sudo mkdir -p /var/tmp/airgap-root
# 빈 루트 기준으로 의존성을 전부 풀어서 받는다
sudo dnf download \
--installroot=/var/tmp/airgap-root \
--releasever=9.4 \
--setopt=reposdir=/etc/yum.repos.d \
--resolve --alldeps \
--destdir=/var/tmp/airgap-bundle/rpms \
httpd mod_ssl
--setopt=reposdir=를 붙인 이유도 문서에 있습니다. "configuration file and reposdir are searched inside the installroot first. If they are not present, they are taken from the host system." 빈 루트에는 저장소 설정이 없으므로 호스트 것을 쓰게 되는데, 문서는 명령줄로 경로를 지정하면 "this path is always relative to the host with no exceptions"라고 못박습니다. 즉 명시적으로 적어 두는 편이 예측 가능합니다.
모듈러 시스템, 즉 RHEL 8이나 모듈을 쓰는 RHEL 9라면 한 줄이 더 필요합니다. 문서는 --setopt=module_platform_id= 를 지정하지 않으면 "the module_platform_id value will be taken from the /etc/os-release file within the installroot (and thus it will be empty at the time of creation, the modular dependency could be unsatisfied and modules content could be excluded)"라고 경고합니다. 모듈 콘텐츠가 조용히 빠진다는 뜻이라 특히 위험합니다. 이 부분은 5편에서 이어서 다룹니다.
dnf reposync — 저장소를 통째로 복제하기
앞으로 무엇을 설치할지 모른다면 저장소 자체를 옮기는 편이 낫습니다. 공식 문서의 설명은 "reposync makes local copies of remote repositories. Packages that are already present in the local directory are not downloaded again"입니다. 두 번째 문장 덕분에 재실행이 증분으로 동작합니다.
# BaseOS 저장소를 메타데이터까지 함께 로컬로 복제한다
sudo dnf reposync \
--repoid=rhel-9-for-x86_64-baseos-rpms \
--download-path=/var/tmp/airgap-bundle/repos \
--download-metadata \
--gpgcheck \
--newest-only \
--arch=x86_64 --arch=noarch \
--remote-time
옵션별로 문서가 말하는 바는 이렇습니다.
| 옵션 | 문서상 동작 |
|---|---|
-p, --download-path | "Root path under which the downloaded repositories are stored, relative to the current working directory. Defaults to the current working directory." |
--download-metadata | 저장소 메타데이터를 함께 받아 그대로 저장소로 쓸 수 있게 합니다 |
-g, --gpgcheck | "Remove packages that fail GPG signature checking after downloading. Exit code is 1 if at least one package was removed." |
-n, --newest-only | "Download only newest packages per-repo." |
-a, --arch | "Download only packages of given architectures (default is all architectures). Can be used multiple times." |
--delete | "Delete local packages no longer present in repository." |
--norepopath | "Don't add the reponame to the download path. Can only be used when syncing a single repository." |
--remote-time | "Try to set the timestamps of the downloaded files to those on the remote side." |
-u, --urls | "Just print urls of what would be downloaded, don't download." |
--source | "Download only source packages." |
--gpgcheck의 종료 코드 동작은 자동화에서 유용합니다. 서명 검증에 실패한 패키지가 하나라도 제거되면 종료 코드가 1이므로, 반입 스크립트에서 그대로 게이트로 쓸 수 있습니다.
--newest-only와 --download-metadata를 같이 쓸 때는 문서의 단서를 기억해야 합니다. 최신 패키지만 내려받지만 메타데이터에는 이전 패키지 정보가 남습니다. 안쪽에서 특정 옛 버전을 요청하면 메타데이터에는 있는데 파일이 없는 상태가 됩니다. 특정 버전 고정이 필요한 환경이라면 --newest-only를 빼는 편이 안전합니다.
반입 전에 복제본이 제대로 저장소로 인식되는지는 문서의 예제 형태로 확인할 수 있습니다.
# 복제한 디렉터리를 임시 저장소로 붙여 목록이 나오는지 본다
dnf --repofrompath=synced,/var/tmp/airgap-bundle/repos/rhel-9-for-x86_64-baseos-rpms \
--repoid=synced list --available | head
--repofrompath는 "Specify a repository to add to the repositories for this query", --repoid는 "Enable just specific repositories by an id or a glob"로 문서화된 전역 옵션입니다.
yumdownloader는 언제 쓰나
yumdownloader는 RHEL 7의 yum-utils 시절 이름입니다. RHEL 8 이후에도 yum-utils 패키지를 설치하면 같은 이름의 명령을 쓸 수 있고, 기존 운영 스크립트를 그대로 유지해야 할 때 의미가 있습니다.
다만 정직하게 적자면, yumdownloader는 dnf-plugins-core 공식 문서에 별도 항목으로 존재하지 않습니다. 문서화된 플러그인 목록에는 download, reposync, repoclosure 등이 있고 yumdownloader는 없습니다. 그래서 이 글은 yumdownloader의 개별 옵션을 단정하지 않습니다. 사용 중인 시스템에서 다음처럼 직접 확인하세요.
# 이 명령이 어느 패키지에서 오는지 확인
dnf provides '*/yumdownloader'
# 실제로 지원하는 옵션 확인
man yumdownloader
yumdownloader --help
새로 절차서를 쓴다면 dnf download를 쓰는 편이 낫습니다. 문서가 있고, 옵션 의미가 명확하며, dnf5에도 대응되는 명령이 있습니다.
받은 것이 완전한지 확인하기
여기가 이 글에서 가장 중요한 부분입니다. 반입 매체를 만들기 전에, 이 묶음만으로 의존성이 다 풀리는지 확인해야 합니다. 폐쇄망에 들어간 뒤에 발견하면 다음 심의까지 기다려야 하기 때문입니다.
dnf repoclosure가 정확히 이 일을 합니다. 문서의 정의는 "repoclosure is a program that reads package metadata from one or more repositories, checks all dependencies, and displays a list of packages with unresolved dependencies"입니다.
# 반입 예정 디렉터리를 저장소로 만들고 (3편에서 자세히 다룹니다)
createrepo_c /var/tmp/airgap-bundle/rpms
# 그 저장소만으로 모든 의존성이 풀리는지 검사한다
dnf repoclosure \
--repofrompath=bundle,/var/tmp/airgap-bundle/rpms \
--repo=bundle
출력이 비어 있으면 통과입니다. 해결되지 않은 의존성이 있으면 패키지별로 나열됩니다. 옵션은 --check <repoid>("Specify repo ids to check, can be specified multiple times"), --pkg <pkg-spec>("Check closure for this package only"), --best("Check only the newest packages per arch")가 있습니다.
이 한 단계를 절차에 넣는 것만으로 반입 재시도의 상당수가 사라집니다.
RHEL 10과 dnf5에서 달라지는 것
RHEL 10은 dnf5 기반입니다. download 명령의 옵션 일부가 이름이 바뀌었으므로 절차서를 그대로 옮기면 안 됩니다.
| 기능 | dnf4 (RHEL 8 / 9) | dnf5 (RHEL 10) |
|---|---|---|
| 소스 RPM 받기 | --source | --srpm |
| 의존성 해결 | --resolve | --resolve |
| 설치된 것도 포함 | --alldeps | --alldeps |
| 받을 위치 | --destdir / --downloaddir | --destdir |
| 특정 저장소로 제한 | --repo | --from-repo |
| 없는 패키지 건너뛰기 | 문서에 해당 옵션 없음 | --skip-unavailable |
dnf5 문서에는 --allmirrors("To be used together with --url. It prints out space-separated URLs from all available mirrors")처럼 dnf4에 없던 옵션도 있습니다. 어느 쪽이든 실행 전에 사용 중인 버전의 man dnf 또는 dnf download --help로 확인하는 습관이 안전합니다.
서브스크립션 없이 Red Hat 콘텐츠를 재배포하는 것은 계약 위반일 수 있으니 조직의 라이선스 조건을 먼저 확인하세요. 이 글의 절차는 엔타이틀먼트가 붙은 연결망 장비에서 실행하는 것을 전제로 합니다.
마치며 — 빈 루트에서 풀고, 반입 전에 닫힘을 확인합니다
이번 편의 결론은 두 줄입니다.
받는 장비의 상태가 결과를 오염시킵니다. --installroot로 빈 루트를 만들고 --releasever를 명시하면 그 오염이 사라집니다. 모듈을 쓰는 시스템이라면 module_platform_id까지 지정해야 모듈 콘텐츠가 빠지지 않습니다.
반입 전에 dnf repoclosure를 돌립니다. 이 검사는 몇 초 걸리고, 통과하지 못한 번들을 들고 들어가면 며칠이 걸립니다.
명령과 옵션은 2026-08-15에 공식 문서에서 확인했습니다. RHEL 버전에 따라 다르므로 사용 중인 버전의 문서로 다시 확인하세요.
직접 해보기
- 리눅스 터미널 — 긴 dnf 명령을 줄바꿈과 함께 조립해 보기
- 리눅스 명령어 퀴즈 — 패키지 관리 옵션 복습
- 해시 생성기 — 받은 rpm의 체크섬을 만들어 4편 매니페스트 미리 연습하기