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, $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
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 Function │
│ Same inputs → Same outputs │
│ /nix/store/<hash>-<name> │
│ │
│ 언어: │
│ 순수 함수형, 게으른 평가 │
│ String, Int, List, AttrSet, Function │
│ let/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.nix │
│ Override, Overlay │
│ fetchurl/fetchFromGitHub (+ hash) │
│ buildFHSEnv (FHS 호환) │
│ │
│ 통합: │
│ direnv (자동 환경) │
│ GitHub Actions + magic-nix-cache │
│ Docker (nix로 이미지 빌드) │
│ │
│ 장점: │
│ 진짜 재현성 │
│ 여러 버전 공존 │
│ 원자적 롤백 │
│ 선언적 │
│ │
│ 단점: │
│ 가파른 학습 곡선 │
│ 에러 메시지 │
│ 일부 소프트웨어 호환성 │
│ 디스크 사용량 │
└─────────────────────────────────────────────────────┘
18. 퀴즈
**A.** **같은 입력은 반드시 같은 출력을 낸다**는 수학적 보장. 전통 빌드는 현재 시간, 호스트명, `/usr/local`의 파일들, 환경 변수, 네트워크 상태 등에 의존해서 "불순"하다. 두 번 빌드하면 다른 결과 가능. Nix는 빌드를 **샌드박스**에서 실행해서 외부 상태를 차단하고, 모든 입력(소스 해시 + 의존성 해시 + 빌드 스크립트 + 환경)을 단일 해시로 요약한다. 이 해시가 출력 경로(`/nix/store/<hash>-<name>`)를 결정. 결과: 내 머신에서 빌드한 결과와 네 머신에서 빌드한 결과가 **bit-for-bit 동일**. 이것이 "works on my machine"의 근본 해결.
**A.** **불변성(immutability)이 재현성의 기반**이기 때문. 한 번 빌드된 패키지는 절대 수정되지 않아야 한다. 수정되면 해시가 "이 경로의 진짜 내용"을 더 이상 증명하지 못한다. 그래서 Nix는 빌드 직후 `/nix/store/<hash>-<name>/`을 read-only로 만들고 손상 시 즉시 감지할 수 있도록 한다. 이 덕분에 여러 버전이 안전하게 공존(Python 3.9와 3.12가 다른 경로에), garbage collection이 안전(참조 없는 것만 삭제), rollback이 단순(과거 세대는 그대로 남아있음). 변경 없음이 모든 안전성의 원천이다.
**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는 "강력한 아이디어를 실제 사용 가능하게" 만든 변화.
**A.** **추상화 수준과 재현성의 결**. Docker는 **런타임 격리** (프로세스/파일시스템/네트워크 namespace로 런타임에 격리된 컨테이너). 이미지를 빌드할 때 여전히 `apt-get install`이 비결정적일 수 있고, 이미지 크기가 크다. Nix는 **빌드 시점 격리** — 각 패키지가 해시 기반 경로에 격리되어 있어 런타임에 컨테이너 없이도 충돌 없음. 이미지 없이 바이너리 하나만 돌려도 충돌 없다. Nix로 Docker 이미지를 빌드하는 조합(nix + dockerTools)이 인기 — Nix의 재현성 + Docker의 배포 표준. 실제 차이: Docker는 "블랙박스 이미지", Nix는 "투명한 빌드 레시피".
**A.** **전체 OS 상태의 선언적 명시**. 전통 Linux에서 "이 서버를 재구축하세요"는 악몽 — 어떤 패키지가 설치됐는지, 어떤 서비스가 활성화됐는지, `/etc/nginx/nginx.conf`가 어떻게 수정됐는지 재현 불가. NixOS는 이 **모든 것**을 `configuration.nix` 파일 하나에. 패키지, 서비스, 사용자, 네트워크, 방화벽, 파일 — 전부 선언적. `nixos-rebuild switch`가 원자적으로 적용. 장점: (1) **버전 관리** (git), (2) **재현 가능** (같은 파일 → 같은 서버), (3) **원자적 롤백** (잘못된 변경 후 한 명령으로 복구), (4) **감사 가능** (어떤 변경이 언제 왜). 프로덕션 서버 운영의 "신의 도구".
**A.** **순수 함수의 대가를 상쇄**. Nix는 순수 함수 원칙이라 같은 입력은 같은 출력 — 즉 한 번 빌드된 결과를 다른 사용자가 재사용할 수 있다. 없으면 모든 사용자가 GCC, Python, Node 같은 큰 패키지를 로컬 빌드해야 하는데 수 시간 걸린다. Binary cache(cache.nixos.org)는 **미리 빌드된 결과를 해시로 주소 지정해서 다운로드** 가능하게 한다. 사용자가 패키지를 요청 → Nix가 해시 계산 → 캐시에서 존재 확인 → 있으면 다운로드(수 초), 없으면 로컬 빌드. Hydra CI가 모든 nixpkgs 커밋을 4개 플랫폼에서 빌드해 캐시에 올린다. 덕분에 Nix가 "이론상 재현 가능하지만 실제로는 빠름"이라는 두 마리 토끼를 잡는다. Cachix 같은 서비스가 팀 빌드 결과 공유를 가능하게 해 CI 시간 극적 감소.
**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/751)
- **Nix**는 "**빌드는 순수 함수**"라는 원칙 위에 세워진 패키지 매니저 + 빌드 시스템.