Nix & NixOS 완벽 가이드 — 재현 가능한 개발 환경·빌드·배포 시스템
이 글의 핵심
Nix는 "모든 빌드를 순수 함수로 표현"하는 패키지 관리자이자 빌드 시스템입니다. 컨테이너가 "어디서나 같은 환경으로 실행"을 목표로 한다면 Nix는 "어디서나 같은 환경을 처음부터 빌드"를 목표로 합니다. 2020년 Flakes 실험 기능 도입 이후 생태계가 성숙해, Nix/NixOS는 개발 환경·CI·서버·데스크탑 설정을 단일 언어로 기술하는 강력한 대안이 되었습니다.
Nix가 해결하는 문제
- “내 컴퓨터에선 되는데”: 팀원마다 Node/Python 버전이 달라 발생
- OS 업그레이드 후 도구 깨짐: 패키지 매니저의 부작용
- 이미지 재현 불가:
FROM node:20도 시간이 지나면 다른 패치가 들어옴 - CI 캐시 관리 복잡: 의존성 설치·빌드 캐시를 도구별로 구현
- 개발자 온보딩 몇 시간/며칠: “이거 저거 설치해주세요”
Nix의 답:
- 모든 패키지가
/nix/store/<hash>-<name>같은 고유 경로 → 버전 충돌 없음 - 입력 해시가 같으면 출력도 같음 → bit-exact 재현
- 선언적
flake.nix로 프로젝트·시스템·서버 환경을 코드화 - 공유 가능한 바이너리 캐시 (cache.nixos.org, Cachix)
설치
Linux/macOS
# Determinate Systems 설치 스크립트 (권장)
curl --proto '=https' --tlsv1.2 -sSf -L \
https://install.determinate.systems/nix | sh -s -- install
# 공식 설치
sh <(curl -L https://nixos.org/nix/install) --daemon
# Flakes 활성화
mkdir -p ~/.config/nix
echo 'experimental-features = nix-command flakes' >> ~/.config/nix/nix.conf
WSL2
WSL2 안에서 Linux 설치 그대로 사용.
첫 flake: 프로젝트 개발 환경
# flake.nix
{
description = "pkglog.com dev env";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};
outputs = { self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (system:
let pkgs = import nixpkgs { inherit system; };
in {
devShells.default = pkgs.mkShell {
packages = with pkgs; [
nodejs_20
pnpm
python3
ripgrep
gh
jq
];
shellHook = ''
echo "Welcome to pkglog dev env"
echo "node: $(node --version)"
'';
};
});
}
nix develop # 셸 진입
# 자동으로 node 20·pnpm·python3·ripgrep·gh·jq 제공
node --version # v20.x
팀원 누구나 git clone 후 nix develop만 하면 같은 버전을 얻습니다.
direnv 연동
brew install direnv
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
cd my-project
echo 'use flake' > .envrc
direnv allow
디렉터리 진입만으로 자동 환경 적용.
devenv: Nix 입문을 쉽게
nix profile install nixpkgs#devenv
devenv init
# devenv.nix
{ pkgs, ... }:
{
packages = [ pkgs.git pkgs.jq ];
languages.javascript = {
enable = true;
package = pkgs.nodejs_20;
pnpm.enable = true;
};
languages.python.enable = true;
services.postgres = {
enable = true;
package = pkgs.postgresql_16;
initialDatabases = [{ name = "app"; }];
};
services.redis.enable = true;
processes.dev.exec = "pnpm dev";
processes.worker.exec = "pnpm worker";
enterShell = ''
echo "Welcome"
'';
pre-commit.hooks = {
prettier.enable = true;
eslint.enable = true;
};
}
devenv shell # 환경 진입
devenv up # PostgreSQL + Redis + dev 서버 + worker 동시 기동
Docker Compose의 역할을 재현 가능한 Nix 기반으로 수행합니다.
여러 아키텍처·CI
outputs = { self, nixpkgs }: {
devShells.x86_64-linux.default = ...;
devShells.aarch64-darwin.default = ...;
packages.x86_64-linux.myapp = ...;
};
GitHub Actions CI:
- uses: DeterminateSystems/nix-installer-action@main
- uses: DeterminateSystems/magic-nix-cache-action@main
- run: nix develop --command bash -c "pnpm install && pnpm test"
magic-nix-cache-action이 GitHub Actions 캐시를 Nix 바이너리 캐시로 사용해 의존성을 극적으로 가속합니다.
패키지 빌드 예시
# flake.nix에 추가
packages.default = pkgs.buildNpmPackage {
pname = "my-cli";
version = "0.1.0";
src = ./.;
npmDepsHash = "sha256-..." ; # 처음엔 lib.fakeHash, 빌드 실패 메시지의 값 복사
installPhase = ''
mkdir -p $out/bin
cp -r dist $out/
makeWrapper ${pkgs.nodejs_20}/bin/node $out/bin/my-cli \
--add-flags $out/dist/cli.js
'';
};
nix build .#default
./result/bin/my-cli
빌드 결과는 /nix/store/<hash>-my-cli-0.1.0/에 저장돼 누구나 동일 해시의 결과를 받을 수 있습니다.
Docker 이미지 빌드
packages.docker = pkgs.dockerTools.buildImage {
name = "my-cli";
tag = "latest";
config = {
Cmd = [ "${self.packages.${system}.default}/bin/my-cli" ];
WorkingDir = "/";
};
};
nix build .#docker
docker load < result
docker run my-cli:latest
이미지 레이어가 입력 해시 기반이라 캐시 재사용이 극대화되고 Dockerfile 없이 선언적 이미지 생성이 가능합니다.
NixOS: 시스템 전체를 선언적으로
# /etc/nixos/configuration.nix
{ config, pkgs, ... }:
{
imports = [ ./hardware-configuration.nix ];
boot.loader.systemd-boot.enable = true;
networking.hostName = "prod-web-1";
networking.networkmanager.enable = true;
users.users.deploy = {
isNormalUser = true;
extraGroups = [ "wheel" "docker" ];
openssh.authorizedKeys.keys = [ "ssh-ed25519 ..." ];
};
environment.systemPackages = with pkgs; [
vim git htop tmux ripgrep jq
];
services.openssh.enable = true;
services.openssh.settings.PasswordAuthentication = false;
services.nginx = {
enable = true;
virtualHosts."pkglog.com" = {
addSSL = true;
enableACME = true;
root = "/var/www/pkglog";
};
};
virtualisation.docker.enable = true;
networking.firewall.allowedTCPPorts = [ 80 443 22 ];
system.stateVersion = "24.11";
}
sudo nixos-rebuild switch
# 한 번의 명령으로 부팅 설정·사용자·패키지·nginx·방화벽이 선언대로 적용
롤백: 잘못되면 sudo nixos-rebuild switch --rollback으로 이전 세대로 즉시 복귀.
Home Manager: 유저 설정을 선언적으로
# ~/.config/home-manager/home.nix
{ pkgs, ... }:
{
home.username = "jb";
home.homeDirectory = "/home/jb";
home.stateVersion = "24.11";
home.packages = with pkgs; [
gh fzf zoxide bat eza
obsidian slack
];
programs.git = {
enable = true;
userName = "JB";
userEmail = "jb@example.com";
extraConfig = {
init.defaultBranch = "main";
pull.rebase = true;
};
};
programs.zsh = {
enable = true;
oh-my-zsh = {
enable = true;
theme = "robbyrussell";
plugins = [ "git" "docker" "kubectl" ];
};
};
programs.vscode = {
enable = true;
extensions = with pkgs.vscode-extensions; [
dbaeumer.vscode-eslint
esbenp.prettier-vscode
];
};
}
home-manager switch
# dotfiles·VS Code 확장·git 설정이 선언대로 적용
새 PC를 받아도 이 파일만 있으면 30분 내 동일 환경 복구.
Nix 언어 기초
# 리터럴
let
n = 42;
s = "hello";
list = [ 1 2 3 ];
set = { a = 1; b = 2; };
in
n + list |> length # 4 (파이프 연산자 2024+)
# 함수
let add = a: b: a + b; # 커링
in add 1 2 # 3
# 조건
if n > 0 then "positive" else "non-positive"
# 구조 분해
{ a, b ? 0, ... }: a + b
# inherit
let pkgs = import <nixpkgs> {}; in
{ inherit (pkgs) git jq ripgrep; }
# import·with
with import <nixpkgs> {}; mkShell { packages = [ nodejs ]; }
바이너리 캐시: 빌드 시간 단축
# /etc/nix/nix.conf 또는 ~/.config/nix/nix.conf
substituters = https://cache.nixos.org https://nix-community.cachix.org
trusted-public-keys = ...
# 팀 전용 캐시
substituters = https://my-team.cachix.org
Cachix에 조직 캐시를 만들고 CI 결과를 push하면 팀원·CI 모두 다운로드로 해결 → 로컬 빌드 시간 0에 가까워짐.
실전 베스트 프랙티스
- flake.lock 커밋: 정확한 의존성 버전 고정
nix fmt사용:nixfmt·alejandra등으로 일관 포맷nix flake check: CI에서 자체 검증- Secrets는 Flake 밖: agenix·sops-nix로 암호화
- Module 분리: 하나의 거대한 flake 대신 여러 모듈로 구성
- Unfree 패키지:
nixpkgs.config.allowUnfree = true;필요 (Chrome·Slack 등)
트러블슈팅
디스크 사용량 폭증
/nix/store가 무한 증가 → 주기적 GC.
nix-collect-garbage -d # 오래된 세대·미사용 패키지 정리
nix-store --optimise # 중복 파일 hard-link
느린 nix develop
- 바이너리 캐시 미사용 → substituters 확인
- Unfree 패키지 재빌드 →
allowUnfree활성
error: flake '...' does not provide attribute 'devShell'
devShells.default 형식인지 확인 (구버전 devShell.system 대비).
macOS에서 unfree 바이너리 실패
일부 바이너리 패키지는 Apple Silicon 미지원. x86_64-darwin으로 Rosetta 실행 또는 소스 빌드.
Flake 업그레이드 깨짐
nix flake update로 lock 갱신 시 입력 변경. --update-input nixpkgs로 개별 업데이트가 안전.
채택 단계별 로드맵
- 개발 환경:
flake.nix+ direnv로 한 프로젝트 시범 - CI: GitHub Actions에 Nix 설치 + 캐시 연동
- 도구 모음:
home-manager로 개인 dotfiles 표준화 - 빌드 파이프라인: npm/cargo/go 빌드를
buildNpmPackage등으로 이관 - 이미지 생성: Dockerfile을
dockerTools로 교체 - NixOS: 서버 또는 개발자 데스크탑 일부를 NixOS로 전환
- 전사 표준화: Cachix·모듈 라이브러리 구축
체크리스트
- Flakes 활성화
- 프로젝트별
flake.nix+flake.lock커밋 - direnv로 자동 환경 로딩
- 팀 바이너리 캐시 (Cachix or self-hosted)
- CI에 Nix + 캐시 연동
-
nix flake check를 CI 통과 조건 - 주기적 GC로 디스크 관리
- Secrets 관리: agenix / sops-nix
- 단계적 채택: devShell → package → image → NixOS
마무리
Nix는 “재현성”이라는 어려운 문제를 가장 근본적으로 해결한 도구입니다. 학습 곡선이 있지만 일단 nix develop 수준만 써도 팀의 환경 동기화·CI 가속이 즉각 개선됩니다. 2026년 현재 Determinate Systems·devenv·Cachix 같은 상용/커뮤니티 인프라가 성숙해 진입 장벽이 크게 낮아졌고, Flakes가 사실상 표준이 되면서 학습 자료도 통일되었습니다. “이미지로는 재현성이 부족하다” “온보딩이 하루씩 걸린다” 같은 문제를 겪는 팀이라면 Nix는 투자 대비 수익이 매우 큰 기술입니다.
관련 글
- Docker 완벽 가이드
- devenv 완벽 가이드
- CI/CD 완벽 가이드
- dotfiles 관리 가이드