더 작은 NixOS ISO를 만들 수 있을까?

2 hours ago 2
  • NixOS는 설정만으로 VM이나 ISO를 만들기 쉽지만, 최소에 가까운 라이브 이미지도 처음부터 458MiB로 생성되어 Alpine VM ISO 약 66MiB와 큰 차이를 보임
  • 크기의 대부분은 nix-store.squashfs가 차지했고, 그 안에는 Python 3.13.13, Linux modules, systemd, Perl, GRUB, 문서, Nix 관련 의존성이 들어 있었음
  • nix.enable = false, documentation.enable = false, register-nix-paths 제거를 거치며 ISO는 458MiB → 384MiB → 360MiB로 줄었고 Boost 의존성도 빠짐
  • OpenSSH 클라이언트, 기본 패키지, GRUB 설치 도구, 런타임 커널 모듈, Perl 기반 활성화 경로까지 걷어내 최종 크기는 183MiB까지 내려감
  • 작은 실험용 부팅 이미지에는 참고할 만하지만, 필요한 기능을 많이 제거하므로 데스크톱이나 중요한 환경에 그대로 쓰기는 어려움

NixOS 설정에서 ISO 만들기

  • NixOS는 설정을 기반으로 VM을 쉽게 만들 수 있음
    • nixos-rebuild build-vm은 현재 시스템 설정의 VM을 생성함
    • pkgs.nixos를 사용하면 시스템 설정이 아니더라도 임의 설정으로 VM을 만들 수 있음
  • 기본 예시는 system.stateVersion = "26.05"와 services.getty.autologinUser = "root"만 둔 최소 VM을 생성함
  • 이 VM은 thin VM으로 동작함
    • 디스크 이미지에는 VM 안에서 직접 만든 파일만 들어감
    • /nix/store 등 나머지는 호스트 OS에서 마운트됨
  • 호스트에 Nix가 없거나 원격 호스트, 일반 하이퍼바이저에서 실행하려면 자체 포함 ISO가 필요함
  • NixOS의 iso-image.nix 모듈을 import하면 ISO를 빌드할 수 있음
    • image.baseName = lib.mkForce "nixos"로 출력 ISO 이름을 지정함
    • 실행 예시는 qemu-system-x86_64 --cdrom .../nixos.iso -m 1G --accel kvm 형태임
    • amd64의 현대 Linux 환경이 아니라면 아키텍처나 가속 방식 변경이 필요할 수 있음

시작점: 458MiB ISO

  • 기본 ISO 빌드 결과는 458MiB였음
  • 이 이미지는 아직 vim도 포함하지 않음
    • 부팅 후 vim 실행 시 command not found가 나옴
  • 비교 대상으로 든 Alpine의 VM ISO는 약 66MiB
  • Damn Small Linux는 훨씬 작은 크기로 완성도 있는 데스크톱 환경을 제공했던 사례로 등장함
  • 목표는 Damn Small Linux 수준에 도달하는 것이 아니라, NixOS ISO를 어느 정도라도 더 줄일 수 있는지 확인하는 데 있음

ISO 내부 크기 분석

  • ISO를 마운트해 du로 확인하니 크기는 다음처럼 나뉨
    • nix-store.squashfs: 416MiB
    • initrd: 26MiB
    • kernel: 13MiB
    • 전체 ISO: 458MiB
  • 핵심 크기 요인은 주 사용자 공간인 nix-store.squashfs였음
  • squashfs를 마운트하면 Nix store처럼 보이는 경로들이 들어 있음
    • python3-3.13.13: 128MiB
    • linux-6.18.35-modules: 144MiB
    • systemd-260.1: 60MiB
    • perl-5.42.0: 56MiB
    • grub-2.12: 여러 항목 합산 약 62MiB
    • nix-manual-2.34.7, nixos-manual-html 등 문서도 포함됨
  • ISO는 호스트에서 빌드되므로, ISO 안의 store 경로는 호스트 /nix/store에서도 추적할 수 있음
  • nix why-depends로 의존성의 출처를 확인함
    • Boost는 Nix daemon 경로를 통해 들어왔음
    • nix-daemon.conf, nix, libnixutil.so를 거쳐 boost-1.89.0에 도달함

Nix와 문서 제거

  • nix.enable = false로 Nix 자체를 이미지에서 제거하려고 시도함
  • documentation.enable = false로 문서도 비활성화함
  • 첫 결과는 458MiB → 384MiB였음
  • 하지만 Boost는 여전히 남아 있었음
    • register-nix-paths.service가 ISO store 내용을 부팅 시 등록하려고 함
    • 이 경로가 다시 Nix와 Boost를 끌어옴
  • systemd.services.register-nix-paths = lib.mkForce {}로 해당 서비스를 비워 제거함
  • 그 결과 ISO는 360MiB가 되었고, nix why-depends에서 Boost 의존성이 없다고 확인됨

OpenSSH와 기본 패키지 제거

  • 비슷한 방식으로 environment.defaultPackages도 비울 수 있었음
  • ssh 제거는 더 까다로웠음
    • modules/programs/ssh.nix가 OpenSSH를 environment.corePackages에 추가함
    • 이를 제어할 programs.ssh.enable 같은 옵션을 찾을 수 없었음
    • services.openssh.enable은 서버 설정이지 클라이언트 제거 옵션이 아님
  • disabledModules로 programs/ssh.nix를 제외할 수는 있었지만, 다른 모듈들이 programs.ssh 옵션 존재를 기대해 연쇄 오류가 발생함
  • 해결책은 programs.ssh 옵션을 쓰지 않는 stub 옵션을 별도 모듈로 제공하는 방식이었음
    • options.programs.ssh = lib.mkOption {};
    • 이후 disabledModules = [ "programs/ssh.nix" ];로 실제 SSH 모듈을 제외함
  • 이 과정에서 다음 설정도 함께 적용함
    • documentation.man.enable = false
    • networking.firewall.enable = false
    • environment.defaultPackages = lib.mkForce []

NixOS 모듈 구조 메모

  • NixOS 모듈은 크게 세 부분을 가짐
    • 모듈 수준 항목: imports, disabledModules
    • 옵션 정의: options.*
    • 구현: config.*
  • 옵션을 정의하지 않는 모듈은 config. 접두어 없이 구현 속성을 쓰는 축약형을 사용할 수 있음
  • 나머지 설정에서 축약형을 유지하기 위해, programs.ssh stub 옵션은 별도 import 모듈로 분리함

GRUB 설치 도구 제거

  • 남은 큰 항목 중 하나는 약 62MiB의 GRUB 관련 파일이었음
  • 부트로더 자체는 필요하지만, 설치 도구를 모두 포함할 필요는 없다고 판단함
  • NixOS ISO preset은 UEFI와 BIOS 버전 GRUB를 모두 번들함
  • 이를 끄는 명확한 옵션은 없어, 더 거친 방식으로 다음 값을 재설정함
    • system.extraDependencies = lib.mkForce []
    • environment.systemPackages = lib.mkForce config.environment.corePackages
  • environment.systemPackages를 완전히 비우지는 않음
    • bash가 없으면 getty가 계속 crashloop할 수 있어, 셸이 어느 정도 동작하도록 corePackages는 유지함

커널 모듈 제거

  • linux-6.18.35-modules는 144MiB였고, 전체 크기의 약 4분의 1에 해당함
  • NixOS에는 런타임에 사용할 커널 모듈을 제한하는 좋은 훅이 보이지 않았음
  • 대신 시스템 출력에서 kernel-modules 폴더를 제거함
    • system.systemBuilderCommands = lib.mkAfter "rm $out/kernel-modules";
  • 이 방식은 런타임 모듈 로딩을 사실상 비활성화
    • 필요한 모듈은 boot.initrd.kernelModules 또는 availableKernelModules에 넣어야 함
  • 이 변경 뒤 더 편한 디스플레이 해상도로 전환하는 기능을 잃었지만, 부팅은 가능했음
  • ISO 크기는 197MiB까지 내려감

Perl 제거와 실험적 대체 기능

  • 여전히 56MiBPerl이 포함되어 있었음
  • nix why-depends로 확인하니 Perl은 시스템 활성화 중 사용자와 /etc를 구성하는 데 쓰였음
  • 사용자와 /etc 구성을 완전히 버릴 수는 없었음
  • 대신 실험적 기능으로 기존 경로를 대체함
    • /etc 관리는 overlay 방식 사용
    • 사용자 관리는 native userborn 사용
  • 적용한 설정은 다음과 같음
    • system.etc.overlay.enable = true
    • system.etc.overlay.mutable = false
    • services.userborn.enable = true
  • 최종 ISO 크기는 183MiB가 됨

최종 상태와 한계

  • 시작점 458MiB에서 최종 183MiB까지 줄어, 원본의 거의 3분의 1 수준이 됨
  • 그래도 결과를 “좋다”고 부르기는 어렵다고 봄
  • 실제로 사용해야 하는 데스크톱이나 중요한 환경에는 적합하지 않음
    • 제거한 기능들은 모두 존재 이유가 있음
  • 작은 실험용 부팅 이미지가 필요하고 아주 작은 작업만 수행하면 되는 경우에는 참고할 수 있음
  • 최종 설정을 그대로 복사해 쓰면 목적에 필요한 기능이 빠져 있을 수 있음

더 줄일 여지

  • 이번 작업은 “그냥 제거 가능”하거나 비교적 명확한 대체 수단이 있는 항목에 집중함
  • 더 깊은 작업이 필요한 영역도 남아 있음
    • 현재 systemdMinimal과 systemd가 모두 번들됨
    • 둘 중 하나를 제거하려고 하면 다른 빌드 경로가 깨졌음
  • 작은 항목들도 더 걷어낼 수 있고, 합산하면 의미 있는 크기가 될 수 있음
  • 추가 최적화에는 더 많은 조사와 실험이 필요함
Read Entire Article