Reproducible Development Environments: Stop the 'Works on My Machine' Era

by Marcus Chen
Reproducible Development Environments: Stop the 'Works on My Machine' Era

Reproducible Development Environments: Hentikan Era 'Works on My Machine'

Seminggu lalu seorang teman mengirim pesan panik: "Kok di laptop gue error, padahal di server jalan?" Klasik. Dia habiskan tiga jam debug, padahal akar masalahnya cuma beda versi Node.js—16.x di laptop, 18.x di server. Tiga jam. Untuk sesuatu yang harusnya tidak pernah terjadi.

Reproducible development environments bukan soal disiplin tim atau dokumentasi yang bagus. Ini soal infrastruktur. Kalau environment-mu tidak bisa direplikasi secara deterministik—sama persis, di mesin mana pun, kapan pun—maka setiap onboarding developer baru, setiap ganti laptop, setiap CI pipeline adalah roulette.

Artikel ini bukan teori. Ini setup yang saya pakai sendiri, dengan tool spesifik, konfigurasi nyata, dan trade-off yang jujur.

Kenapa Environment Tidak Reproducible Itu Mahal

Biaya "works on my machine" sering tidak terlihat sampai menumpuk. Beberapa skenario yang saya alami sendiri:

  • Onboarding baru: Developer junior butuh 2 hari setup environment. Separuhnya dihabiskan tanya-tanya di Slack.
  • Ganti OS: Migrasi dari macOS Intel ke Apple Silicon—tiba-tiba ada dependency yang compiled untuk x86.
  • CI/CD drift: Pipeline hijau, tapi production error karena library system di runner-nya beda.
  • "Upgrade" yang tidak sengaja: Seseorang jalankan brew upgrade dan tiba-tiba Python 3.10 jadi 3.12.

Semua ini punya solusi yang sama: environment harus didefinisikan sebagai kode, bukan sebagai ritual instalasi manual.

Tiga Layer yang Perlu Dikontrol

Sebelum pilih tool, pahami dulu apa yang perlu dikontrol. Ada tiga layer:

1. Runtime & Language Version

Ini yang paling sering jadi sumber masalah. Node 16 vs 18, Python 3.10 vs 3.12, Go 1.21 vs 1.22—semua bisa breaking.

Tool pilihan saya: mise (versi 2024.x)

mise adalah pengganti asdf yang lebih cepat dan lebih waras. Konfigurasinya lewat file .mise.toml di root project:

[tools]
node = "20.11.0"
python = "3.11.8"
go = "1.22.1"

[env]
NODE_ENV = "development"
DATABASE_URL = "postgresql://localhost:5432/myapp_dev"

File ini masuk ke git. Siapa pun yang clone repo dan punya mise installed, jalankan mise install—selesai. Tidak ada ambiguitas.

Kenapa bukan asdf? mise ditulis ulang di Rust, jauh lebih cepat, dan syntax konfigurasinya lebih bersih. Saya migrasi dari asdf di awal 2024 dan tidak menyesal.

2. System Dependencies & Services

Runtime sudah aman, tapi bagaimana dengan PostgreSQL, Redis, atau library sistem seperti libpq? Di sinilah Docker masuk—bukan untuk seluruh development environment, tapi khusus untuk services.

Pendekatan yang saya rekomendasikan: Docker Compose untuk services, bukan untuk app

# docker-compose.yml
services:
  postgres:
    image: postgres:16.2-alpine
    environment:
      POSTGRES_DB: myapp_dev
      POSTGRES_USER: dev
      POSTGRES_PASSWORD: devpassword
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data

  redis:
    image: redis:7.2-alpine
    ports:
      - "6379:6379"

volumes:
  postgres_data:

App-nya tetap jalan di host, bukan di container. Kenapa? Karena hot reload lebih cepat, debugging lebih mudah, dan file watcher tidak butuh konfigurasi tambahan. Container untuk services, host untuk development—ini trade-off yang masuk akal untuk solo engineer.

3. Project Dependencies & Lockfiles

Ini yang paling sering diabaikan: commit lockfile-mu.

  • package-lock.json atau yarn.lock untuk Node
  • poetry.lock untuk Python
  • go.sum untuk Go
  • Cargo.lock untuk Rust

Jangan pernah .gitignore lockfile. Itu dokumen kontrak versi dependency-mu. Tanpa itu, npm install hari ini bisa menghasilkan hasil berbeda dari npm install bulan lalu.

Devcontainer: Ketika Docker Memang Diperlukan

Ada skenario di mana kamu butuh isolasi penuh—misalnya kalau kamu kerja di beberapa project dengan konflik system library yang serius, atau kalau tim-mu campuran Windows/macOS/Linux.

Di sinilah Dev Containers (spec dari Microsoft, bukan hanya VS Code) berguna. File konfigurasinya ada di .devcontainer/devcontainer.json:

{
  "name": "myapp-dev",
  "image": "mcr.microsoft.com/devcontainers/base:ubuntu-22.04",
  "features": {
    "ghcr.io/devcontainers/features/node:1": {
      "version": "20"
    },
    "ghcr.io/devcontainers/features/python:1": {
      "version": "3.11"
    }
  },
  "postCreateCommand": "npm install && pip install -r requirements.txt",
  "forwardPorts": [3000, 8000],
  "mounts": [
    "source=${localWorkspaceFolder},target=/workspace,type=bind"
  ]
}

Spec ini bisa dipakai di VS Code, JetBrains (via plugin), atau bahkan CLI dengan devcontainer CLI tool. Bukan vendor lock-in ke satu editor.

Tapi jujur: kalau kamu solo engineer dengan setup macOS/Linux yang konsisten, mise + Docker Compose untuk services sudah cukup. Dev Containers menambah overhead yang tidak selalu worth it.

Nix: Solusi Paling Deterministik, Tapi Ada Harganya

Tidak bisa nulis artikel tentang reproducible development environments tanpa menyebut Nix. Ini tool yang paling serius soal reproducibility—secara literal, hash dari setiap dependency di-track.

Dengan nix develop dan file flake.nix, kamu bisa define environment yang benar-benar identik di macOS, Linux, bahkan di CI:

{
  description = "myapp dev environment";

  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 = with pkgs; [
            nodejs_20
            python311
            postgresql_16
            redis
          ];
        };
      }
    );
}

Jalankan nix develop—dapat shell dengan semua tool di atas, versi exact, tanpa polusi global.

Trade-off yang harus kamu tahu:

  • Learning curve Nix sangat curam. Bahasanya sendiri (Nix expression language) butuh waktu untuk terbiasa.
  • Error message-nya sering tidak membantu.
  • Waktu setup awal bisa lama karena download banyak.

Rekomendasiku: coba Nix kalau kamu sudah nyaman dengan mise + Docker Compose dan masih punya masalah reproducibility. Jangan mulai dari Nix kalau baru mau mulai.

Automasi Setup dengan Makefile atau Justfile

Tool sudah dipilih, konfigurasi sudah ada di git. Tapi masih ada satu masalah: urutan setup. Developer baru harus tahu harus jalankan apa dulu.

Solusi paling simpel: Makefile atau justfile sebagai entry point.

# Makefile
.PHONY: setup dev test clean

setup: ## Setup environment dari nol
	mise install
	docker compose up -d
	npm install
	cp .env.example .env
	@echo "Setup selesai. Jalankan 'make dev' untuk mulai."

dev: ## Jalankan development server
	docker compose up -d
	npm run dev

test: ## Jalankan test suite
	docker compose up -d
	npm test

clean: ## Hentikan semua services
	docker compose down

Dengan ini, onboarding jadi:

  1. git clone <repo>
  2. make setup
  3. make dev

Tiga langkah. Bukan dua halaman README.

Kalau lebih suka syntax yang lebih bersih, just adalah alternatif yang bagus—syntax-nya lebih ekspresif dan error handling-nya lebih baik dari Make.

CI/CD: Pastikan Pipeline Pakai Environment yang Sama

Semua setup di atas percuma kalau CI-mu pakai environment berbeda. Ini yang sering dilupakan.

Contoh GitHub Actions yang konsisten dengan setup lokal:

# .github/workflows/test.yml
name: Test

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v4

      - name: Install mise
        uses: jdx/mise-action@v2
        # Otomatis baca .mise.toml dan install tools

      - name: Start services
        run: docker compose up -d

      - name: Install dependencies
        run: npm ci  # bukan npm install

      - name: Run tests
        run: npm test

Perhatikan npm ci bukan npm install. npm ci strict terhadap lockfile—kalau ada perbedaan antara package.json dan package-lock.json, dia error. Itulah yang kamu mau di CI.

Juga: pin versi ubuntu-22.04, bukan ubuntu-latest. ubuntu-latest bisa berubah kapan saja dan merusak reproducibility yang sudah kamu bangun.

Perbandingan Tool: Pilih yang Sesuai Kebutuhan

Tool Use Case Learning Curve Reproducibility
mise Runtime version management Rendah Tinggi
Docker Compose Service isolation Rendah-Sedang Tinggi
Dev Containers Full env isolation Sedang Sangat Tinggi
Nix Flakes Maximum reproducibility Sangat Tinggi Maksimal

Untuk sebagian besar solo engineer: mise + Docker Compose + lockfiles sudah 90% dari jalan. Dev Containers kalau butuh portabilitas lintas OS. Nix kalau kamu mau all-in.

Kalau kamu tertarik dengan pendekatan self-hosted yang lebih jauh, this guide on self-hosted e-commerce platform comparison 2024 bisa memberikan perspektif tentang infrastruktur yang bisa jadi bagian dari setup development environment-mu.

Dan kalau CI/CD self-hosted juga menarik, ada juga artikel tentang Woodpecker CI untuk pipeline lokal yang pakai Docker Compose sebagai dasarnya.

Kesimpulan: Apa yang Harus Kamu Lakukan Besok

Reproducible development environments bukan proyek besar yang butuh waktu berbulan-bulan. Mulai kecil, tapi mulai sekarang.

Besok pagi, ambil satu project yang paling sering bikin masalah setup. Lakukan ini:

  1. Install mise, tambahkan .mise.toml dengan versi exact dari semua runtime yang dipakai.
  2. Buat docker-compose.yml untuk semua external services (database, cache, queue).
  3. Commit lockfile—kalau belum ada, generate sekarang.
  4. Tulis Makefile dengan target setup, dev, dan test.

Total waktu: 1-2 jam. Manfaatnya: tidak ada lagi debug "works on my machine" yang makan waktu berjam-jam.

Kalau sudah nyaman dengan empat langkah itu, baru pertimbangkan Dev Containers atau Nix. Tapi untuk sekarang—mulai dari yang simpel dan konsisten.