Masalahnya: Dependency Tabrakan di Mesin Sendiri
Kamu punya dua proyek: satu butuh Node 16, satu lagi butuh Node 20. Atau Python 3.9 vs 3.11. Solusi biasanya: nvm, pyenv, rbenv — satu tool per bahasa, config tersebar, dan tetap saja kadang konflik.
Gue pernah di titik di mana npm install di proyek lama tiba-tiba error karena proyek baru upgrade Node global. Spent dua jam debugging, padahal masalahnya cuma version mismatch yang nggak kelihatan.
Solusi yang sekarang gue pakai: Nix Shell — khususnya nix develop dengan flake. Tiap proyek punya environment yang terisolasi, reproducible, dan nggak nyentuh sistem global sama sekali. Kalau kamu belum pernah dengar Nix, ini bukan tentang NixOS (distro Linux-nya). Ini tentang Nix sebagai package manager yang bisa jalan di Linux dan macOS.
Kenapa Nix, Bukan Docker?
Docker itu powerful, tapi overhead-nya nyata:
- Setiap proyek perlu
Dockerfile,docker-compose.yml - Build image lama kalau dari scratch
- Volume mount di macOS masih lambat untuk I/O intensif
- Kamu tetap ngedit di luar container, tapi run di dalam — context switching nyebelin
Nix Shell berbeda: environment-nya aktif langsung di shell kamu. Nggak ada container, nggak ada network bridge. Kamu cd ke folder proyek, jalankan satu command, dan semua tool yang dibutuhkan langsung tersedia — versi yang tepat, terisolasi dari sistem.
Kalau kamu keluar dari shell itu, semua dependency itu "menghilang" dari PATH. Mesin kamu tetap bersih.
Install Nix Dulu
Cara paling gampang adalah pakai Determinate Systems Nix Installer — ini lebih reliable daripada official installer, terutama di macOS:
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
Setelah install, restart terminal kamu. Verifikasi:
nix --version
# nix (Nix) 2.18.x
Pastikan experimental features aktif. Cek ~/.config/nix/nix.conf atau /etc/nix/nix.conf:
experimental-features = nix-command flakes
Kalau belum ada, tambahkan baris itu.
Setup Proyek dengan Nix Flake
Struktur yang gue pakai untuk tiap proyek baru adalah flake.nix di root direktori. Ini contoh untuk proyek Node.js + PostgreSQL client tools:
{
description = "Node.js project environment";
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 = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
buildInputs = [
pkgs.nodejs_20
pkgs.nodePackages.pnpm
pkgs.postgresql_15
pkgs.curl
pkgs.jq
];
shellHook = ''
echo "Node $(node --version) ready"
echo "pnpm $(pnpm --version) ready"
export DATABASE_URL="postgresql://localhost:5432/mydb"
'';
};
});
}
Simpan file ini sebagai flake.nix di root proyek. Lalu:
nix develop
Pertama kali bakal download packages — agak lama. Tapi setelah itu, semua di-cache. Kalau rekan kerja kamu clone repo yang sama dan jalankan nix develop, mereka dapat environment yang identik.
Contoh Nyata: Python + FastAPI
Ini yang gue pakai untuk proyek FastAPI:
{
description = "FastAPI dev environment";
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 = nixpkgs.legacyPackages.${system};
python = pkgs.python311;
pythonPackages = python.pkgs;
in
{
devShells.default = pkgs.mkShell {
buildInputs = [
python
pythonPackages.fastapi
pythonPackages.uvicorn
pythonPackages.sqlalchemy
pythonPackages.alembic
pythonPackages.httpx
pythonPackages.pytest
pkgs.ruff
];
shellHook = ''
echo "Python $(python --version) ready"
export PYTHONDONTWRITEBYTECODE=1
'';
};
});
}
Jalankan:
nix develop
python -c "import fastapi; print(fastapi.__version__)"
# 0.111.x
Semua package Python itu terisolasi. Kalau kamu keluar dari shell dan cek Python global, FastAPI nggak akan ada di sana.
Gotcha yang Perlu Kamu Tahu
1. Package Python yang Nggak Ada di nixpkgs
Nggak semua package Python ada di nixpkgs. Kalau kamu butuh package obscure, ada dua opsi:
Opsi A: Gunakan venv di dalam nix shell. Nix menyediakan Python interpreter, kamu tetap bisa pip install untuk package yang nggak ada:
shellHook = ''
python -m venv .venv
source .venv/bin/activate
pip install -q some-obscure-package
'';
Opsi B: Pakai pkgs.python311.withPackages untuk lebih banyak kontrol:
buildInputs = [
(pkgs.python311.withPackages (ps: with ps; [
fastapi
uvicorn
]))
];
2. nix develop Lambat Pertama Kali
Ini normal. Nix download dan build packages dari source atau binary cache. Setelah cached, masuk ke environment hampir instan. Kamu bisa tambahkan binary cache dari Cachix untuk mempercepat:
nix profile install nixpkgs#cachix
cachix use nix-community
3. File flake.lock Harus Di-commit
Ini penting. flake.lock menyimpan exact version dari semua dependency. Tanpa ini, nix develop di mesin lain bisa resolve ke versi berbeda. Selalu commit flake.lock ke Git.
git add flake.nix flake.lock
git commit -m "chore: add nix dev environment"
4. Masalah di macOS dengan Beberapa Package
Beberapa package Linux-only nggak tersedia di macOS. Kalau kamu develop di macOS tapi deploy ke Linux, hati-hati dengan package yang platform-specific. Gunakan kondisional kalau perlu:
buildInputs = [
pkgs.nodejs_20
] ++ pkgs.lib.optionals pkgs.stdenv.isLinux [
pkgs.inotify-tools
];
Integrasi dengan direnv (Otomatis Aktif)
Manual nix develop setiap kali masuk folder itu agak repot. Solusinya: direnv + nix-direnv.
Install direnv:
# Di NixOS atau via nix
nix profile install nixpkgs#direnv nixpkgs#nix-direnv
# Hook ke shell kamu (bash)
echo 'eval "$(direnv hook bash)"' >> ~/.bashrc
# Atau zsh
echo 'eval "$(direnv hook zsh)"' >> ~/.zshrc
Buat file .envrc di root proyek:
use flake
Lalu:
direnv allow
Sekarang setiap kali kamu cd ke folder proyek, environment Nix otomatis aktif. Keluar dari folder, environment otomatis nonaktif. Nggak perlu ingat command apapun.
Output yang kamu lihat di terminal:
direnv: loading ~/projects/myapp/.envrc
direnv: using flake
Node v20.x.x ready
pnpm 9.x.x ready
direnv: export +DATABASE_URL +PATH ...
Struktur File di Proyek Kamu
Ini struktur yang gue rekomendasikan:
myproject/
├── flake.nix # definisi environment
├── flake.lock # lock file, selalu commit
├── .envrc # "use flake" untuk direnv
├── .gitignore
│ └── (tambahkan: .direnv/)
├── src/
└── ...
Tambahkan .direnv/ ke .gitignore — itu folder cache direnv yang nggak perlu di-commit:
echo '.direnv/' >> .gitignore
Langkah Lanjutan
Ini yang gue lakuin setelah setup dasar ini jalan:
1. Template flake.nix per stack — Gue punya repo private dengan template untuk Node, Python, Go, dan Rust. Setiap proyek baru tinggal copy template yang sesuai.
2. Tambahkan development scripts ke shellHook — Misalnya auto-start database local, generate .env dari template, atau run migration:
shellHook = ''
echo "Starting dev environment..."
if [ ! -f .env ]; then
cp .env.example .env
echo ".env created from template"
fi
'';
3. Explore home-manager — Kalau kamu mau selangkah lebih jauh, home-manager bisa manage dotfiles dan user-level packages dengan Nix. Tapi ini topik sendiri.
4. Pin nixpkgs ke versi stabil — Untuk production-critical projects, ganti nixos-unstable ke release stabil seperti nixos-24.05 supaya nggak ada surprise update.
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
Nix punya learning curve yang lumayan — syntax-nya asing dan error message-nya kadang kriptikal. Tapi setelah dua minggu pakai, gue nggak mau balik ke cara lama. Tiap proyek berdiri sendiri, onboarding rekan kerja tinggal nix develop, dan mesin gue tetap bersih dari tumpukan package global yang nggak jelas asalnya.