Skip to content

✍️ 필사 모드: Nix Package Manager Deep Dive — 재현 가능한 빌드, Derivation, Flakes, NixOS 완전 정복 (2025)

한국어
0%
정확도 0%
💡 왼쪽 원문을 읽으면서 오른쪽에 따라 써보세요. Tab 키로 힌트를 받을 수 있습니다.

TL;DR

  • Nix는 "빌드는 순수 함수"라는 원칙 위에 세워진 패키지 매니저 + 빌드 시스템.
  • 핵심 아이디어: build(inputs) → output. 입력(소스, 의존성, 빌드 스크립트)의 해시가 출력 경로를 결정. 같은 입력이면 반드시 같은 결과.
  • /nix/store/<hash>-<name>: 모든 빌드 결과가 이 디렉토리에 저장. 여러 버전이 공존, 충돌 없음.
  • Derivation (.drv): 빌드 레시피의 직렬화. "이런 입력으로 이렇게 빌드하라"는 청사진.
  • Nix 언어: 순수 함수형. 게으른 평가. Derivation을 값으로 다룸.
  • nixpkgs: 세계 최대 패키지 저장소. 100,000+ 패키지. Homebrew, apt, yum의 합보다 크다.
  • Flakes (2021+): 재현 가능성을 기본으로 만든 새 인터페이스. flake.nix + flake.lock.
  • NixOS: Nix 기반 Linux 배포판. 시스템 설정을 선언적으로. configuration.nix 하나가 전체 OS.
  • "Works on my machine" 해결: 여러 Python/Node 버전 공존, 매 프로젝트에 격리된 환경, 정확한 재현.
  • 단점: 가파른 학습 곡선, 디버깅 어려움, 일부 생태계 불편 (dynamic link 소프트웨어).

1. Nix가 해결하는 문제

1.1 "Works on my machine"의 오래된 저주

개발자 A: "이 코드 돌려줘." 개발자 B: "에러 나." A: "내 머신에선 되는데?"

근본 원인:

  • 다른 OS (Ubuntu 20.04 vs 22.04).
  • 다른 라이브러리 버전 (libssl 1.1 vs 3).
  • 다른 Python/Node 버전.
  • 환경 변수 차이.
  • /usr/local의 쓰레기.

전통적 해결:

  • README에 "apt install ..." 적기: 수동, 놓치기 쉬움.
  • Docker: 강력하지만 무겁고 빌드가 느림.
  • asdf, mise: 언어 버전 관리, OS 수준 의존성은 X.

1.2 진짜 재현성의 기준

"재현 가능하다"의 의미는:

  1. 같은 코드를 두 번 빌드 → 같은 바이너리 (byte-for-byte).
  2. 다른 머신에서도 같은 결과.
  3. 1년 후에도 같은 결과 (의존성 업스트림 변경 없이).

이것이 진짜 어렵다. 빌드 시간, 호스트명, 랜덤 시드, 컴파일러 버전 — 모두 영향.

1.3 Nix의 약속

"If two builds have the same inputs, they produce the same output. Period."

Nix는 이 원칙을 강제한다 — 샌드박스, 순수 함수, 해시 기반 주소로.

결과: 어떤 머신에서 돌려도 같은 결과. 1년 후에도. 10년 후에도.

1.4 역사적 맥락

Eelco Dolstra의 2003년 박사 논문 "The Purely Functional Software Deployment Model"에서 시작. Utrecht 대학. 아이디어는 급진적이었다:

"왜 소프트웨어 배포를 함수형 프로그래밍처럼 다루지 않는가?"

2003-2006: 학술 프로젝트. NixOS 탄생 (2003). 2006-2015: 느린 성장. 팬덤 형성. 2015-2020: 주류화 시작. 기업 채택. 2021: Flakes 출시. 큰 전환점. 2024-2025: 엔터프라이즈 채택 본격화. Determinate Systems 등 상용 지원.


2. 핵심 개념

2.1 Pure Function as Build

전통 빌드:

make  # /usr/local/lib을 수정, /tmp 쓰기, 네트워크 fetch, 현재 시간 사용

불순(impure). 외부 상태에 의존.

Nix 빌드:

build(
    sources = hash("foo-1.2.tar.gz"),
    deps = [hash(libcurl), hash(openssl), hash(gcc)],
    buildScript = hash("./configure && make && make install"),
    env = {"CC": "gcc"}
)/nix/store/abc123-foo-1.2/

순수 함수. 같은 입력 → 같은 해시 → 같은 출력. 다른 입력 → 다른 해시 → 다른 경로.

2.2 /nix/store

모든 빌드 결과가 여기에:

/nix/store/
├── 0k7n3f0bd3...-gcc-11.3.0/
├── 1q8f2r9vn4...-openssl-3.0.5/
├── 2h5m1j7xb9...-python-3.10.6/
├── 3b2s7k8pc6...-my-app-0.1/
└── ...

각 디렉토리:

  • 해시 prefix: 빌드 입력의 해시 (32자 base32).
  • 이름: 사람이 읽기 위함.

Read-only. 빌드 완료 후 변경 불가. 손상되면 해시 불일치 → 감지.

2.3 여러 버전 공존

/nix/store/abc123-python-3.9.0/
/nix/store/def456-python-3.10.0/
/nix/store/ghi789-python-3.11.0/

Python 3.9, 3.10, 3.11이 동시에 존재. 충돌 없음. 각자 자기 디렉토리.

심볼릭 링크가 현재 프로파일을 가리킴:

~/.nix-profile/bin/python → /nix/store/def456-python-3.10.0/bin/python

다른 프로파일로 전환하면 링크만 바뀜. "설치/제거" 대신 "심볼릭 링크 업데이트".

2.4 Derivation

빌드 레시피. .drv 파일:

Derive([
  ("out", "/nix/store/abc123-hello-2.12.1", "", "")],
  [("/nix/store/xxx-gcc-11.3.0.drv", ["out"]),
   ("/nix/store/yyy-bash-5.1.drv", ["out"])],
  ["/nix/store/zzz-hello-2.12.1.tar.gz"],
  "x86_64-linux",
  "/nix/store/www-bash-5.1/bin/bash",
  ["-e", "/nix/store/vvv-default-builder.sh"],
  [("buildInputs", ""),
   ("name", "hello-2.12.1"),
   ("out", "/nix/store/abc123-hello-2.12.1"),
   ("src", "/nix/store/zzz-hello-2.12.1.tar.gz"),
   ("system", "x86_64-linux")])

필드:

  • outputs: 결과 경로 (해시 포함).
  • inputDrvs: 의존하는 다른 derivation들.
  • inputSrcs: 소스 파일들.
  • platform: 타겟 아키텍처.
  • builder: 빌드 실행 프로그램 (보통 bash).
  • args: builder에 전달할 인자.
  • env: 환경 변수.

이 전체를 직렬화한 해시가 output 경로의 prefix.

2.5 Nix 언어

.nix 파일은 함수형 언어:

{ stdenv, fetchurl, gcc }:

stdenv.mkDerivation {
  pname = "hello";
  version = "2.12.1";

  src = fetchurl {
    url = "https://ftp.gnu.org/gnu/hello/hello-2.12.1.tar.gz";
    sha256 = "0xw6cr5jgi1ir13q6apvrivwmmpr5j8vbymp0x6ll0kcv6366hnn";
  };

  buildInputs = [ gcc ];

  buildPhase = ''
    ./configure --prefix=$out
    make
  '';

  installPhase = ''
    make install
  '';
}

특징:

  • 순수 함수형: 부수효과 없음 (거의).
  • 게으른 평가: 필요할 때만 계산.
  • 타입: 동적, 간단 (string, int, list, attrset, function).
  • let ... in, with, inheriting (inherit) 같은 구문.

Haskell 영향이 강함.

2.6 Build Sandbox

빌드가 정말 순수한지 보장하기 위해 샌드박스에서 실행:

  • 별도 mount namespace: 빌드 디렉토리 외 접근 불가.
  • 네트워크 차단 (fetch는 사전 hash 검증 필수).
  • 임시 파일 시스템: 빌드 끝나면 모두 제거.
  • 고정 USER,USER, HOME: 호스트 정보 숨김.
  • LD_LIBRARY_PATH 없음: 오직 선언된 의존성만.

이 때문에 Nix 패키지가 "빌드 중 임의 파일을 만지는" 것은 불가능.


3. Nix 언어 상세

3.1 기본 타입

# 문자열
"hello"
''
  멀티라인
  들여쓰기 유지
''

# 숫자
42
3.14

# 논리
true
false

# Null
null

# 리스트
[ 1 2 3 "four" ]

# Attribute set (dict와 유사)
{ name = "Alice"; age = 30; }

# 함수
x: x + 1

# 다인자 함수 (currying)
x: y: x + y

# Attribute set 파라미터 (destructuring)
{ name, age }: "${name} is ${toString age}"

3.2 Let / With

let
  x = 1;
  y = 2;
in
  x + y  # 3

# with: attribute set을 scope에 펼치기
with { a = 1; b = 2; };
  a + b  # 3

3.3 Lazy Evaluation

let
  slow = builtins.trace "computed!" 42;
in
  "ignore"  # slow가 평가되지 않음 → "computed!" 출력 안 됨

Haskell처럼 필요할 때만 평가. 큰 nixpkgs 파일도 lazy 덕분에 빠르게.

3.4 Imports

# default.nix
{ pkgs ? import <nixpkgs> {} }:

pkgs.stdenv.mkDerivation {
  # ...
}

<nixpkgs>는 channel 또는 flake에서 온 pkgs. 파일 경로도 가능.

3.5 Function Application

let
  add = x: y: x + y;
in
  add 1 2  # 3, currying

3.6 Rec (재귀적 attribute set)

rec {
  x = 1;
  y = x + 1;  # rec 덕분에 같은 set 안의 x 참조 가능
}
# { x = 1; y = 2; }

3.7 Derivation 생성

builtins.derivation이 최저 수준. 실무에선 stdenv.mkDerivation wrapper 사용:

stdenv.mkDerivation {
  pname = "myapp";
  version = "1.0";
  src = ./.;
  buildInputs = [ pkgs.openssl pkgs.libffi ];
  nativeBuildInputs = [ pkgs.pkg-config pkgs.cmake ];
  
  configurePhase = "cmake -DCMAKE_INSTALL_PREFIX=$out .";
  buildPhase = "make";
  installPhase = "make install";
}

nativeBuildInputs: 빌드 시에만 (컴파일러 등). buildInputs: 런타임 의존성.


4. 실전 사용

4.1 설치

macOS/Linux:

curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install

Determinate Systems의 설치기 권장 (공식보다 유연).

NixOS: OS가 이미 Nix.

4.2 패키지 설치

# Imperative (전통)
nix-env -iA nixpkgs.hello
hello  # 실행

# Declarative (선호)
# ~/.config/nixpkgs/home.nix 또는 NixOS configuration.nix 편집
home.packages = [ pkgs.hello ];

4.3 nix-shell — 임시 환경

nix-shell -p python3 nodejs_20 postgresql_15
# 새 셸. 여기서만 python3, node 20, postgresql 15 사용 가능
exit  # 원래 환경으로

이것이 Nix의 가장 강력한 기능. 시스템 설치 없이 임시 환경.

# 스크립트에 shebang
#!/usr/bin/env nix-shell
#! nix-shell -i python3 -p python3 python3Packages.requests
import requests
print(requests.get("https://example.com").text)

이 스크립트는 어떤 머신에서든 Python과 requests 라이브러리로 실행.

4.4 shell.nix

프로젝트별 dev 환경:

# shell.nix
{ pkgs ? import <nixpkgs> {} }:

pkgs.mkShell {
  buildInputs = with pkgs; [
    rustc
    cargo
    rust-analyzer
    pkg-config
    openssl
  ];
  
  shellHook = ''
    echo "Welcome to my-rust-project"
  '';
}

nix-shell 입력 시 이 환경 진입. 프로젝트의 모든 기여자가 같은 rust 버전, 같은 openssl 버전 사용.

4.5 direnv 통합

direnv: 디렉토리 진입 시 자동 환경 설정.

# .envrc
use nix

cd my-project/하면 자동으로 shell.nix 로드. 나가면 원래대로.

nix-direnv 플러그인은 캐싱 추가 → 즉시 진입.

4.6 빌드

nix-build default.nix
# 결과: ./result → /nix/store/...-my-app-0.1/

result는 심볼릭 링크. 실제는 store에.

4.7 Update

nix-channel --update
nix-env -u

Channel (nixpkgs 버전)을 업데이트, 설치된 패키지 업그레이드.


5. Flakes — 재현성의 완성

5.1 Classic Nix의 문제

2021년 이전 Nix의 단점:

  • Channel 기반 nixpkgs: 버전 명시 X.
  • Nix 파일 간 참조: 경로/import 혼란.
  • 재현성 미보장: "이 shell.nix는 내 머신에서 되는데 네 머신에서 안 됨".
  • 공유 어려움: "nix-env -iA nixpkgs.x가 매번 다른 버전".

5.2 Flakes 도입 (2021)

공식적으로는 여전히 "실험적" 이지만 실제 사용자의 90%가 Flakes.

flake.nix:

{
  description = "My Rust project";

  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11";
    flake-utils.url = "github:numtide/flake-utils";
  };

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system:
      let pkgs = nixpkgs.legacyPackages.${system}; in
      {
        devShells.default = pkgs.mkShell {
          buildInputs = [ pkgs.rustc pkgs.cargo ];
        };
        packages.default = pkgs.rustPlatform.buildRustPackage {
          pname = "myapp";
          version = "0.1.0";
          src = ./.;
          cargoLock.lockFile = ./Cargo.lock;
        };
      }
    );
}

flake.lock (자동 생성):

{
  "nodes": {
    "nixpkgs": {
      "locked": {
        "lastModified": 1700000000,
        "narHash": "sha256-...",
        "owner": "NixOS",
        "repo": "nixpkgs",
        "rev": "abc123...",
        "type": "github"
      }
    }
  }
}

핵심: flake.lock이 정확한 nixpkgs 리비전을 고정. 같은 flake를 다른 머신에서 빌드하면 bit-for-bit 동일한 결과.

5.3 명령어

# 환경 진입
nix develop

# 빌드
nix build

# 실행 (원격도 가능)
nix run github:some/project

# Flake 업데이트
nix flake update

# 특정 input만 업데이트
nix flake lock --update-input nixpkgs

5.4 Flakes의 이점

  • 재현성: flake.lock 공유 → 완벽 재현.
  • Composability: Flake가 다른 flake를 input으로.
  • 표준 구조: outputs.packages.<system>.<name>, devShells.<system>.<name> 등.
  • Registry: nix run nixpkgs#hello처럼 간편 참조.

5.5 Flake는 아직 실험적?

공식 상태: 실험적. 하지만:

  • NixOS 커뮤니티 90%가 사용.
  • Determinate Systems는 기본 활성화.
  • 안정화는 정치적 이슈 (Nix 창립자 Eelco와의 의견 차이).
  • 2025년 현재 안정화 작업 진행 중.

사실상 표준.


6. NixOS — 선언적 OS

6.1 개념

NixOS는 리눅스 배포판. 특별한 점: 전체 시스템 설정을 하나의 파일에.

# /etc/nixos/configuration.nix
{ config, pkgs, ... }:

{
  # 부트로더
  boot.loader.systemd-boot.enable = true;

  # 호스트명
  networking.hostName = "my-laptop";

  # 타임존
  time.timeZone = "Asia/Seoul";

  # 사용자
  users.users.alice = {
    isNormalUser = true;
    extraGroups = [ "wheel" "docker" ];
    shell = pkgs.zsh;
  };

  # 시스템 패키지
  environment.systemPackages = with pkgs; [
    vim
    firefox
    git
    docker
  ];

  # 서비스
  services.openssh.enable = true;
  services.nginx = {
    enable = true;
    virtualHosts."example.com" = {
      root = "/var/www";
      enableACME = true;
      forceSSL = true;
    };
  };

  services.postgresql = {
    enable = true;
    package = pkgs.postgresql_15;
    authentication = ''
      local all all trust
    '';
  };

  # 네트워킹
  networking.firewall.allowedTCPPorts = [ 80 443 22 ];

  system.stateVersion = "23.11";
}

이 파일 하나가 전체 OS 상태. 패키지, 서비스, 사용자, 네트워크, 방화벽, 시스템 설정 전부.

6.2 nixos-rebuild

sudo nixos-rebuild switch

이 명령이:

  1. configuration.nix 평가.
  2. 필요한 파생 (derivation) 빌드.
  3. 새 system profile 생성.
  4. Boot loader에 등록.
  5. 변경된 서비스 재시작.

결과: 새 상태로 전환. 원자적.

6.3 Rollback

sudo nixos-rebuild switch --rollback

즉시 이전 상태로. Boot 메뉴에도 이전 세대 선택 가능 → bricked 시스템에서 부팅 복구.

6.4 테스트

sudo nixos-rebuild test

재부팅 없이 임시 적용. 재부팅하면 원래 상태.

sudo nixos-rebuild dry-run

실제로 뭐가 바뀔지 미리 보기.

6.5 장점

  • 선언적: "어떻게 설치할까"가 아니라 "무엇을 원하는가".
  • 원자적: 실패 시 전체 롤백.
  • 재현 가능: 같은 설정 파일 → 같은 시스템.
  • 버전 관리: configuration.nix를 git에.
  • 세대 관리: 모든 이전 상태 복구 가능.

6.6 단점

  • 학습 곡선: Nix 언어 + 방대한 옵션 문서.
  • 비표준 경로: /usr/local/bin 없음. /nix/store/만.
  • 동적 링크 이슈: 미리 빌드된 바이너리가 동작 안 할 수 있음 (해결: nix-ld 또는 Flakes).
  • 디스크 공간: /nix/store가 크기 증가.

7. Home Manager

7.1 사용자 수준 선언

NixOS가 시스템 전체를 관리한다면, Home Manager사용자 환경을 관리.

# ~/.config/home-manager/home.nix
{ config, pkgs, ... }:

{
  home.username = "alice";
  home.homeDirectory = "/home/alice";
  home.stateVersion = "23.11";

  home.packages = with pkgs; [
    ripgrep
    fd
    bat
    eza
  ];

  programs.git = {
    enable = true;
    userName = "Alice";
    userEmail = "alice@example.com";
    aliases = {
      st = "status";
      co = "checkout";
    };
  };

  programs.neovim = {
    enable = true;
    extraConfig = ''
      set number
      set tabstop=4
    '';
  };

  programs.zsh = {
    enable = true;
    oh-my-zsh = {
      enable = true;
      theme = "robbyrussell";
      plugins = [ "git" "docker" ];
    };
  };
}

**home-manager switch**로 적용.

7.2 장점

  • Dotfile 버전 관리: 모든 dotfile이 재생성.
  • 크로스 머신: 같은 home.nix로 여러 머신.
  • NixOS 불필요: Mac, Ubuntu 같은 다른 OS에서도 Home Manager.

7.3 크로스 플랫폼 개발 환경

# 개인 dotfiles repo의 flake.nix
{
  inputs.home-manager.url = "github:nix-community/home-manager";
  
  outputs = { self, nixpkgs, home-manager, ... }: {
    homeConfigurations."alice@laptop" = home-manager.lib.homeManagerConfiguration {
      pkgs = nixpkgs.legacyPackages.x86_64-linux;
      modules = [ ./home.nix ];
    };
    homeConfigurations."alice@mac" = home-manager.lib.homeManagerConfiguration {
      pkgs = nixpkgs.legacyPackages.aarch64-darwin;
      modules = [ ./home.nix ];
    };
  };
}

Mac과 Linux에서 같은 설정. OS 고유 부분만 조건부.


8. nixpkgs

8.1 세계 최대 패키지 저장소

nixpkgs는 Nix의 공식 패키지 저장소. 2025년 기준:

  • 100,000+ 패키지.
  • 9,000+ 기여자.
  • 월 수백 개 새 패키지.

Homebrew (~7,000), AUR (~70,000)보다 많음. Debian (~60,000)과 비교해도 방대.

8.2 저장소 구조

nixpkgs/
├── pkgs/
│   ├── applications/
│   │   ├── editors/
│   │   │   ├── vim/
│   │   │   └── emacs/
│   │   ├── graphics/
│   │   ├── networking/
│   │   └── ...
│   ├── development/
│   │   ├── compilers/
│   │   │   ├── gcc/
│   │   │   ├── rust/
│   │   │   └── ...
│   │   ├── libraries/
│   │   └── python-modules/
│   ├── servers/
│   ├── tools/
│   └── top-level/
│       ├── all-packages.nix
│       └── ...
├── nixos/
│   ├── modules/
│   ├── release.nix
│   └── ...
└── lib/
  • pkgs/: 모든 패키지.
  • nixos/modules/: NixOS 옵션 모듈 (설정 옵션 정의).
  • lib/: 재사용 라이브러리.

8.3 기여 방법

git clone https://github.com/NixOS/nixpkgs
cd nixpkgs
# 새 패키지 추가
vim pkgs/applications/misc/myapp/default.nix
# 변경 사항 빌드
nix-build -A myapp
# PR 제출

기여 시:

  • 테스트 필수.
  • 메인테이너 지정.
  • hydra (CI)가 모든 플랫폼에서 빌드.

8.4 Hydra

Hydra는 Nix의 빌드 서비스. 모든 nixpkgs 커밋에 대해:

  • x86_64-linux, aarch64-linux, x86_64-darwin, aarch64-darwin 빌드.
  • 결과를 바이너리 캐시(cache.nixos.org)에.

사용자가 패키지를 요청하면:

  1. Derivation 계산.
  2. 해시가 바이너리 캐시에 있는지 확인.
  3. 있으면 다운로드 (빠름).
  4. 없으면 로컬 빌드 (느림).

이 덕분에 평균 사용자는 빌드 없이 패키지 설치.


9. Binary Cache

9.1 역할

Nix의 순수성 덕분에 해시가 같으면 결과도 같다. 그래서 누군가 빌드한 결과를 다른 사람이 재사용 가능.

cache.nixos.org:

  • Hydra가 빌드.
  • 공식 바이너리.
  • 전 세계 CDN.

9.2 커스텀 캐시

자체 패키지도 공유 가능:

# 빌드
nix-build

# 업로드
nix copy --to s3://my-bucket result/

# 다른 머신에서 설정
nix.settings.substituters = [ "s3://my-bucket" ];
nix.settings.trusted-public-keys = [ "mykey:..." ];

팀 내부 캐시, 빌드 결과 공유.

9.3 Cachix

Cachix (오픈소스 서비스). 커스텀 binary cache를 쉽게.

cachix use myproject
# 이제 myproject.cachix.org에서 자동 다운로드

CI에서 빌드 후 Cachix 업로드 → 팀원들이 CI 결과 재사용 → 빌드 시간 극적 감소.

9.4 Self-hosted

Nix는 간단한 HTTP server로 바이너리 캐시 가능. nix-serve.

nix-serve --port 8080 &
# 이 머신을 바이너리 캐시로 사용 가능

10. Dev Shells와 CI

10.1 개발 환경

# flake.nix
devShells.default = pkgs.mkShell {
  buildInputs = with pkgs; [
    nodejs_20
    yarn
    postgresql
    redis
  ];
  
  shellHook = ''
    export DATABASE_URL="postgresql://localhost/myapp"
    echo "Welcome to myapp dev environment"
  '';
};

nix develop로 진입. 팀원 누구나 동일 환경.

10.2 GitHub Actions

name: CI
on: [push]
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - uses: DeterminateSystems/nix-installer-action@main
    - uses: DeterminateSystems/magic-nix-cache-action@main
    - run: nix develop --command cargo test

magic-nix-cache: 자동 캐시 공유. CI 속도 극적 개선.

10.3 재현 가능한 릴리스

nix build .#my-app
# ./result에 결과물

다음 달, 다음 해에 같은 리비전으로 빌드 → bit-for-bit 동일. 감사, 보안, 재생산에 중요.


11. Common Patterns

11.1 Override

기본 패키지를 수정:

let
  myVim = pkgs.vim.override {
    features = "huge";
    python3Support = true;
  };
in
  myVim

11.2 Overlay

nixpkgs 자체를 커스터마이즈:

let
  overlays = [
    (self: super: {
      myPackage = super.callPackage ./my-package.nix {};
      python3 = super.python311;  # 기본 python을 3.11로
    })
  ];
  pkgs = import <nixpkgs> { inherit overlays; };
in
  pkgs

11.3 Fetchurl / FetchGit

외부 소스 가져오기:

src = fetchurl {
  url = "https://example.com/foo-1.0.tar.gz";
  sha256 = "sha256-abc...";
};

src = fetchFromGitHub {
  owner = "user";
  repo = "project";
  rev = "v1.0.0";
  sha256 = "sha256-def...";
};

Hash 필수. 없으면 빌드 거부. 보장된 입력.

11.4 buildFHSEnv

일부 소프트웨어가 /usr/bin 같은 FHS 경로를 가정 → Nix store에선 문제. FHS 샌드박스:

buildFHSEnv {
  name = "my-fhs-env";
  targetPkgs = pkgs: with pkgs; [ libz openssl ];
  runScript = "bash";
}

이 환경 안에선 /usr/lib가 Nix 의존성에 매핑됨. Steam, Discord 같은 앱에 필요.

11.5 mkShell Combinations

여러 shell을 조합:

devShells.rust = pkgs.mkShell { buildInputs = [ pkgs.rustc pkgs.cargo ]; };
devShells.node = pkgs.mkShell { buildInputs = [ pkgs.nodejs ]; };
devShells.default = pkgs.mkShell {
  inputsFrom = [ devShells.rust devShells.node ];
};

12. Nix vs 경쟁자

12.1 Docker

Docker:

  • 장점: 표준화, 생태계 방대, 런타임 격리.
  • 단점: 이미지 크기 크다, 빌드 느리다, 재현성 일부 부족.

Nix:

  • 장점: 진짜 재현성, 작은 산출물, 빠른 빌드 (캐시), 여러 버전 공존.
  • 단점: 학습 어렵다, 일부 소프트웨어 호환 문제.

함께 사용: Nix로 Docker 이미지 빌드. dockerTools.buildLayeredImage.

12.2 Guix

Guix (GNU 프로젝트): Nix의 사촌. 같은 아이디어, Scheme 언어.

차이:

  • 언어: Nix는 Nix 언어, Guix는 Scheme.
  • 정치: Guix는 FSF, 자유 소프트웨어만.
  • 커뮤니티: Guix가 훨씬 작음.
  • nixpkgs: Nix가 패키지 수 압도.

Guix가 학습하기 더 쉽다는 평 (Scheme). 하지만 Nix 생태계가 훨씬 크다.

12.3 Homebrew / apt / yum

전통 패키지 매니저. 장점: 간단. 단점:

  • 한 버전만 설치 가능.
  • 글로벌 상태 (/usr/local, /etc).
  • 재현성 부족.
  • 한 시스템의 변경이 다른 곳에 영향.

Nix는 완전 격리. 전통 PM과 철학이 근본적으로 다름.

12.4 asdf / mise

언어 버전 관리:

  • asdf: 언어별 버전 스위치.
  • mise: asdf의 Rust 재작성, 더 빠름.

장점: 단순. 단점: 언어만 관리, 시스템 라이브러리 X.

Nix는 언어 + 시스템 + 빌드 도구 + 서비스 모두.

12.5 Bazel / Buck

대형 프로젝트용 빌드 시스템. 재현성 강조.

  • Bazel: 빠른 빌드, 큰 프로젝트 (Google, Dropbox).
  • Buck2: Meta의 Rust로 재작성.
  • Nix: 패키징 + 빌드.

Bazel은 한 프로젝트 내의 재현성, Nix는 시스템 + 의존성 재현성. 보완 가능.


13. 실전 시나리오

13.1 다중 Python 프로젝트

프로젝트 A: Python 3.9, Django 3. 프로젝트 B: Python 3.12, FastAPI.

전통 방식:

  • pyenv + virtualenv + requirements.txt.
  • 에러 많음.
  • 다른 팀원 환경 불일치.

Nix 방식:

프로젝트 A의 flake.nix:

devShells.default = pkgs.mkShell {
  buildInputs = [ pkgs.python39 pkgs.python39Packages.django_3 ];
};

프로젝트 B의 flake.nix:

devShells.default = pkgs.mkShell {
  buildInputs = [ pkgs.python312 pkgs.python312Packages.fastapi ];
};

cd A && nix develop → Python 3.9 환경. cd B && nix develop → Python 3.12 환경.

충돌 없음. direnv로 자동 전환.

13.2 프로덕션 배포

NixOS 서버에:

services.nginx = {
  enable = true;
  virtualHosts."myapp.com" = {
    locations."/" = {
      proxyPass = "http://localhost:8080";
    };
  };
};

systemd.services.myapp = {
  description = "My App";
  wantedBy = [ "multi-user.target" ];
  serviceConfig = {
    ExecStart = "${pkgs.myapp}/bin/myapp";
    Restart = "always";
  };
};

nixos-rebuild switch로 배포.

A/B 배포: 두 NixOS 세대가 있음. 새 세대가 잘못되면 이전으로 원클릭 롤백.

13.3 Monorepo

# flake.nix
{
  outputs = { self, nixpkgs }: {
    packages.x86_64-linux = {
      frontend = nixpkgs.legacyPackages.x86_64-linux.callPackage ./frontend {};
      backend = nixpkgs.legacyPackages.x86_64-linux.callPackage ./backend {};
      cli = nixpkgs.legacyPackages.x86_64-linux.callPackage ./cli {};
    };
    
    devShells.x86_64-linux.default = nixpkgs.legacyPackages.x86_64-linux.mkShell {
      buildInputs = with nixpkgs.legacyPackages.x86_64-linux; [
        nodejs_20
        rustc
        cargo
        go
      ];
    };
  };
}

한 저장소에 여러 언어 프로젝트, 공통 dev shell.


14. 흔한 함정

14.1 "Nix는 어려워요"

진실이다. 학습 곡선이 가파름.

완화:

  • 공식 튜토리얼보다 Zero to Nix (Determinate Systems) 권장.
  • Flakes 먼저 배우기. Classic Nix는 나중에.
  • 작은 프로젝트부터: nix develop만 사용.

14.2 에러 메시지

Nix의 에러는 악명 높다. 거대한 trace, 의미 파악 어려움.

error: attempt to call something which is not a function but a set, 
at /nix/store/.../pkgs/top-level/all-packages.nix:1234:5
...

완화:

  • Nix 2.18+에서 에러 메시지 개선.
  • --show-trace 상세 정보.
  • 커뮤니티 디스코드 활용.

14.3 동적 라이브러리

Nix store의 바이너리는 rpath가 Nix store를 가리킴. 그런데 바이너리를 /usr/local/bin으로 복사하면 의존 라이브러리가 엉뚱한 곳에서 찾음.

해결:

  • Nix 환경 안에서 실행.
  • nix-ld: 동적 링커를 통해 호환.
  • patchelf로 rpath 수정.

14.4 디스크 사용량

/nix/store가 계속 자람. 이전 패키지가 GC 대상:

nix-collect-garbage -d  # 모든 세대, 모든 참조 없는 것 제거
nix-collect-garbage --delete-older-than 30d  # 30일 이전만

14.5 빌드 시간

바이너리 캐시에 없는 패키지는 로컬 빌드. GCC 컴파일은 시간 걸림.

해결:

  • 공식 캐시 사용 (nixpkgs 패키지는 대부분 prebuilt).
  • Cachix로 팀 캐시 공유.
  • 큰 의존성은 캐시 가능한 flake input으로.

15. 미래와 도전

15.1 Flakes 안정화

"Experimental"에서 벗어나 공식 표준화. 2024-2025 진행 중.

15.2 nix3 명령어

nix-env, nix-shell 같은 레거시 대신 nix install, nix develop으로 통합. 점진적.

15.3 Determinate Systems

Eelco Dolstra + 일부 핵심 개발자가 설립한 회사. 상용 지원, 설치기, 매니지드 바이너리 캐시.

15.4 Content-Addressed Derivations

현재 derivation은 "입력 기반 해시". 실제 출력이 같아도 입력 sha256이 다르면 다른 경로.

CA derivation: 출력 자체를 해시. 같은 출력 = 같은 경로. 더 나은 캐싱. 2024+ 실험적.

15.5 생태계

  • nix-darwin: macOS를 NixOS처럼.
  • deploy-rs: 원격 NixOS 배포.
  • colmena: 여러 NixOS 호스트 관리.
  • disko: 디스크 파티션도 선언적.

16. 학습 로드맵

1단계: 체험

  • Nix 설치 (Determinate Systems installer).
  • nix run nixpkgs#hello 실행.
  • nix develop nixpkgs#python312 진입.

2단계: Flakes 기초

  • 작은 프로젝트에 flake.nix 추가.
  • devShell 작성.
  • direnv 통합.

3단계: 패키징

  • 간단한 앱을 Nix로 빌드.
  • callPackage, mkDerivation 이해.

4단계: NixOS

  • VM으로 NixOS 시작.
  • 개인 설정 작성.
  • Home Manager.

자료:

  • Zero to Nix (Determinate Systems).
  • Nix Pills (전통적, 깊이 있음).
  • NixOS 위키.
  • @Misterio77의 star-collection flake (설정 예제).

:

  • "NixOS in Production" (2024+).
  • "Nix for Python Developers".

YouTube:

  • Vimjoyer, Home Manager 영상.
  • NixCon 발표.

17. 요약 — 한 장 정리

┌─────────────────────────────────────────────────────┐
Nix Cheat Sheet├─────────────────────────────────────────────────────┤
│ 핵심 원칙:Build = Pure FunctionSame inputs → Same outputs                          │
/nix/store/<hash>-<name>│                                                       │
│ 언어:│   순수 함수형, 게으른 평가                              │
String, Int, List, AttrSet, Functionlet/in, with, rec                                  │
│                                                       │
Derivation:│   빌드 레시피 (.drv)Inputs: src, deps, script, env                     │
Output: /nix/store/ 경로                            │
│                                                       │
Flakes (2021+):│   flake.nix + flake.lock│   재현 가능성 보장                                     │
│   inputs + outputs                                    │
│   nix develop / build / run                          │
│                                                       │
NixOS:│   전체 OS를 configuration.nix에│   nixos-rebuild switch│   원자적 + 롤백 가능                                   │
│                                                       │
Home Manager:│   사용자 환경 관리                                     │
Cross-platform (Mac + Linux)Dotfile 버전 관리                                   │
│                                                       │
│ nixpkgs:100,000+ 패키지                                    │
│   세계 최대 저장소                                     │
Hydra가 prebuilt                                    │
│                                                       │
Binary Cache:│   cache.nixos.org (공식)Cachix (상용)Self-hosted 가능                                    │
│                                                       │
│ 주요 명령:│   nix develop    (dev shell)│   nix build      (빌드)│   nix run        (실행)│   nix flake update (업데이트)│                                                       │
│ 패턴:│   shell.nix / flake.nixOverride, Overlay│   fetchurl/fetchFromGitHub (+ hash)buildFHSEnv (FHS 호환)│                                                       │
│ 통합:direnv (자동 환경)GitHub Actions + magic-nix-cache                   │
Docker (nix로 이미지 빌드)│                                                       │
│ 장점:│   진짜 재현성                                          │
│   여러 버전 공존                                       │
│   원자적 롤백                                          │
│   선언적                                               │
│                                                       │
│ 단점:│   가파른 학습 곡선                                     │
│   에러 메시지                                          │
│   일부 소프트웨어 호환성                                │
│   디스크 사용량                                        │
└─────────────────────────────────────────────────────┘

18. 퀴즈

Q1. Nix의 "빌드는 순수 함수"란 무엇을 의미하는가?

A. 같은 입력은 반드시 같은 출력을 낸다는 수학적 보장. 전통 빌드는 현재 시간, 호스트명, /usr/local의 파일들, 환경 변수, 네트워크 상태 등에 의존해서 "불순"하다. 두 번 빌드하면 다른 결과 가능. Nix는 빌드를 샌드박스에서 실행해서 외부 상태를 차단하고, 모든 입력(소스 해시 + 의존성 해시 + 빌드 스크립트 + 환경)을 단일 해시로 요약한다. 이 해시가 출력 경로(/nix/store/<hash>-<name>)를 결정. 결과: 내 머신에서 빌드한 결과와 네 머신에서 빌드한 결과가 bit-for-bit 동일. 이것이 "works on my machine"의 근본 해결.

Q2. /nix/store/ 디렉토리가 왜 read-only인가?

A. 불변성(immutability)이 재현성의 기반이기 때문. 한 번 빌드된 패키지는 절대 수정되지 않아야 한다. 수정되면 해시가 "이 경로의 진짜 내용"을 더 이상 증명하지 못한다. 그래서 Nix는 빌드 직후 /nix/store/<hash>-<name>/을 read-only로 만들고 손상 시 즉시 감지할 수 있도록 한다. 이 덕분에 여러 버전이 안전하게 공존(Python 3.9와 3.12가 다른 경로에), garbage collection이 안전(참조 없는 것만 삭제), rollback이 단순(과거 세대는 그대로 남아있음). 변경 없음이 모든 안전성의 원천이다.

Q3. Flakes가 Classic Nix를 어떻게 개선했는가?

A. 재현성을 기본값으로 만들었다. Classic Nix는 <nixpkgs> 채널 참조로 인해 "내 shell.nix가 내 머신에선 작동하지만 네 머신에선 다르다"는 문제가 있었다. 채널 버전이 지정되지 않아 각자 다른 리비전을 보기 때문. Flakes는 flake.lock 파일로 모든 input의 정확한 commit hash를 고정 — Cargo.lock, package-lock.json과 같은 역할. 또한 표준 구조(outputs.packages.<system>.<name>, devShells, nixosConfigurations)를 강제해서 flake 간 상호 운용성이 보장. 결과: "이 flake를 클론하고 nix develop → 1년 전 환경 완벽 재현". Classic Nix가 "강력한 아이디어지만 사용 불편"이었다면 Flakes는 "강력한 아이디어를 실제 사용 가능하게" 만든 변화.

Q4. Nix가 Docker와 다른 점은?

A. 추상화 수준과 재현성의 결. Docker는 런타임 격리 (프로세스/파일시스템/네트워크 namespace로 런타임에 격리된 컨테이너). 이미지를 빌드할 때 여전히 apt-get install이 비결정적일 수 있고, 이미지 크기가 크다. Nix는 빌드 시점 격리 — 각 패키지가 해시 기반 경로에 격리되어 있어 런타임에 컨테이너 없이도 충돌 없음. 이미지 없이 바이너리 하나만 돌려도 충돌 없다. Nix로 Docker 이미지를 빌드하는 조합(nix + dockerTools)이 인기 — Nix의 재현성 + Docker의 배포 표준. 실제 차이: Docker는 "블랙박스 이미지", Nix는 "투명한 빌드 레시피".

Q5. NixOS의 "configuration.nix"가 가져오는 이점은?

A. 전체 OS 상태의 선언적 명시. 전통 Linux에서 "이 서버를 재구축하세요"는 악몽 — 어떤 패키지가 설치됐는지, 어떤 서비스가 활성화됐는지, /etc/nginx/nginx.conf가 어떻게 수정됐는지 재현 불가. NixOS는 이 모든 것configuration.nix 파일 하나에. 패키지, 서비스, 사용자, 네트워크, 방화벽, 파일 — 전부 선언적. nixos-rebuild switch가 원자적으로 적용. 장점: (1) 버전 관리 (git), (2) 재현 가능 (같은 파일 → 같은 서버), (3) 원자적 롤백 (잘못된 변경 후 한 명령으로 복구), (4) 감사 가능 (어떤 변경이 언제 왜). 프로덕션 서버 운영의 "신의 도구".

Q6. Binary cache가 왜 Nix 사용 경험의 핵심인가?

A. 순수 함수의 대가를 상쇄. Nix는 순수 함수 원칙이라 같은 입력은 같은 출력 — 즉 한 번 빌드된 결과를 다른 사용자가 재사용할 수 있다. 없으면 모든 사용자가 GCC, Python, Node 같은 큰 패키지를 로컬 빌드해야 하는데 수 시간 걸린다. Binary cache(cache.nixos.org)는 미리 빌드된 결과를 해시로 주소 지정해서 다운로드 가능하게 한다. 사용자가 패키지를 요청 → Nix가 해시 계산 → 캐시에서 존재 확인 → 있으면 다운로드(수 초), 없으면 로컬 빌드. Hydra CI가 모든 nixpkgs 커밋을 4개 플랫폼에서 빌드해 캐시에 올린다. 덕분에 Nix가 "이론상 재현 가능하지만 실제로는 빠름"이라는 두 마리 토끼를 잡는다. Cachix 같은 서비스가 팀 빌드 결과 공유를 가능하게 해 CI 시간 극적 감소.

Q7. Nix가 "학습 곡선이 가파른" 이유와 완화 방법은?

A. 세 가지 난이도가 합쳐져 있다: (1) Nix 언어 — 순수 함수형, 동적 타입, 게으른 평가, 희소한 문법. 하스켈 경험이 없으면 생소. (2) Nix 모델 — 모든 것이 derivation, store path, 순수성 등 개념 재설정 필요. "apt-get install"에 익숙한 사람에게는 이상함. (3) 에러 메시지 — 거대한 trace, 의미 파악 어려움. "undefined variable foo"가 attribute set에 없는 키인지 오타인지 찾기 힘들다. 완화: (1) Zero to Nix (Determinate Systems)으로 Flakes 중심 학습, Classic Nix는 나중에. (2) 작게 시작nix develop만 써보고, nixpkgs 기여는 나중에. (3) 커뮤니티 — NixOS Discord, Matrix는 친절하고 활발. (4) 실제 예시 — GitHub의 dotfiles flake들을 따라하기. (5) Nix 2.18+의 개선된 에러 메시지 사용. "1-2주의 고생을 감수하면 평생의 생산성"이 Nix의 광고 문구.


이 글이 도움이 됐다면 다음 포스트도 확인해 보세요:

  • "Git Internals Deep Dive" — 또 다른 content-addressable 시스템.
  • "Docker BuildKit & Image Layers Deep Dive" — 비교 대상 기술.
  • "Terraform Internals Deep Dive" — 선언적 인프라의 다른 접근.
  • "Container Internals (cgroups, namespaces, overlayfs, runc)" — 격리의 원리.

현재 단락 (1/772)

- **Nix**는 "**빌드는 순수 함수**"라는 원칙 위에 세워진 패키지 매니저 + 빌드 시스템.

작성 글자: 0원문 글자: 21,248작성 단락: 0/772