DevOps & Cloud

NixOS and Nix for Containers

GRATIS

Kuasai NixOS dan Nix untuk reproducible builds, containers, development environments, flakes, dan deployment yang deterministic

1. Apa itu Nix?

Nix adalah package manager dan build system yang menjamin reproducible builds. Berbeda dari package manager tradisional (apt, brew, npm), Nix menggunakan pendekatan functional — setiap package di-build di isolated environment dan disimpan di Nix Store dengan hash unik. NixOS adalah distribusi Linux yang dikonfigurasi sepenuhnya menggunakan Nix language.

Nix Ecosystem
📦
Nix Package Manager
Cross-platform
Isolated builds
Rollback support
🐧
NixOS
Declarative OS config
Atomic upgrades
Generations
❄️
Nix Flakes
Pinned dependencies
Reproducible inputs
Standard structure
🐳
Nix Containers
Minimal images
No Dockerfile needed
Nix-built images

1.1 Nix vs Docker

FiturNixDocker
Build SystemFunctional, hash-addressedLayer-based (Dockerfile)
ReproducibilityGuaranteed (content-addressed)Best-effort
SharingBinary cache, deduplicationImage layers
RollbackInstant, atomicRebuild dari image lama
OS ConfigDeclarative (NixOS)Dockerfile (imperative)
Image SizeMinimal (hanya runtime deps)Base image + layers

2. Instalasi Nix

Terminal — Install Nix
# Single-user install (Linux/macOS)
sh <(curl -L https://nixos.org/nix/install) --no-daemon

# Multi-user install (recommended untuk production)
sh <(curl -L https://nixos.org/nix/install) --daemon

# Setelah install, restart shell
# Verifikasi
nix --version

# Aktifkan experimental features (diperlukan untuk flakes)
mkdir -p ~/.config/nix
cat > ~/.config/nix/nix.conf << 'EOF'
experimental-features = nix-command flakes
max-jobs = auto
auto-optimise-store = true
EOF

# Cek Nix store
nix-store --query --requisites /run/current-system | head -20

# Install NixOS (opsional, untuk VM/remote)
# Download ISO dari https://nixos.org/download.html
# Atau gunakan nixos-infect untuk convert VPS ke NixOS
curl -L https://raw.githubusercontent.com/elitak/nixos-infect/master/nixos-infect | bash

3. Nix Language Basics

Nix language fundamentals
# Nix expression language — functional, lazy, dynamically typed

# Variables dan tipe data
let
  name = "my-app";
  version = "1.0.0";
  debug = false;
  numbers = [ 1 2 3 4 5 ];
  config = {
    host = "localhost";
    port = 8080;
    tls = true;
  };
in {
  inherit name version;  # shorthand untuk name = name; version = version;
}

# Functions
let
  # Function dengan satu parameter
  greet = name: "Hello, ${name}!";

  # Function dengan multiple arguments (currying)
  multiply = a: b: a * b;

  # Function dengan pattern matching
  process = { name, version ? "0.0.1", ... }: "${name}-${version}";
in {
  greeting = greet "World";         # "Hello, World!"
  result = multiply 3 4;            # 12
  package = process { name = "app"; };  # "app-0.0.1"
}

# Let-in expressions
let
  pkgs = import  {};
  python = pkgs.python312;
in
pkgs.mkShell {
  buildInputs = [
    python
    python.pkgs.requests
    python.pkgs.flask
    pkgs.nodejs_20
  ];
}

# Import & Fetch
let
  # Import dari file lokal
  config = import ./config.nix;

  # Fetch dari URL
  src = fetchTarball {
    url = "https://github.com/my-org/my-app/archive/v1.0.tar.gz";
    sha256 = "abc123...";
  };

  # Fetch dari Git
  flake = fetchGit {
    url = "https://github.com/NixOS/nixpkgs.git";
    rev = "abc123def456...";
  };
in
  # ...

4. Nix Flakes

Flakes adalah mekanisme standar untuk mendefinisikan Nix project dengan pinned dependencies. Flakes menggantikan channel-based system dengan file flake.nix yang menentukan semua input secara eksplisit.

flake.nix — Project dengan Nix Flakes
{
  description = "My Node.js application";

  # Input dependencies (semua di-pin)
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
    flake-utils.url = "github:numtide/flake-utils";
    devenv.url = "github:cachix/devenv";
  };

  # Outputs
  outputs = { self, nixpkgs, flake-utils, devenv }:
    flake-utils.lib.eachDefaultSystem (system:
      let
        pkgs = nixpkgs.legacyPackages.${system};
        nodejs = pkgs.nodejs_20;
      in {
        # Development shell
        devShells.default = pkgs.mkShell {
          buildInputs = [
            nodejs
            pkgs.nodePackages.npm
            pkgs.nodePackages.prettier
            pkgs.docker
            pkgs.kubectl
            pkgs.terraform
          ];
          shellHook = ''
            echo "🚀 Development environment ready!"
            echo "Node.js: $(node --version)"
            echo "npm: $(npm --version)"
          '';
        };

        # Build package
        packages.default = pkgs.buildNpmPackage {
          pname = "my-app";
          version = "1.0.0";
          src = ./.;
          npmDepsHash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";

          buildPhase = ''
            npm run build
          '';

          installPhase = ''
            mkdir -p $out/bin
            cp -r dist/* $out/
            makeWrapper ${nodejs}/bin/node $out/bin/my-app \
              --add-flags "$out/server.js"
          '';
        };

        # Docker image
        packages.dockerImage = pkgs.dockerTools.buildLayeredImage {
          name = "my-app";
          tag = "latest";
          contents = [
            self.packages.${system}.default
            pkgs.cacert    # SSL certificates
            pkgs.busybox   # Minimal shell (untuk debug)
          ];
          config = {
            Cmd = [ "/bin/my-app" ];
            ExposedPorts = { "8080/tcp" = {}; };
            Env = [
              "NODE_ENV=production"
              "SSL_CERT_FILE=${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt"
            ];
            Labels = {
              "org.opencontainers.image.source" = "https://github.com/my-org/my-app";
            };
          };
        };
      }
    );
}
Terminal — Flake commands
# Initialize flake di project baru
nix flake init

# Update dependencies
nix flake update

# Update specific input
nix flake update nixpkgs

# Enter development shell
nix develop
# Atau dengan specific shell
nix develop .#default

# Build package
nix build

# Build Docker image
nix build .#dockerImage

# Load ke Docker
docker load < result

# Run
docker run -p 8080:8080 my-app:latest

# Check flake.lock
nix flake metadata

5. Nix Containers

Nix bisa menghasilkan container images yang minimal — hanya berisi runtime dependencies yang benar-benar dibutuhkan. Tidak perlu Dockerfile, semua di-build dari Nix expressions.

Nix container build methods
# Method 1: buildLayeredImage — layered, optimized
packages.dockerImage = pkgs.dockerTools.buildLayeredImage {
  name = "my-app";
  tag = "latest";

  # Contents: packages yang ada di dalam container
  contents = [
    self.packages.${system}.default
    pkgs.cacert
    # JANGAN taruh pkgs.bash atau tools lain
    # kecuali benar-benar diperlukan
  ];

  # Layer yang paling sering berubah di layer paling atas
  maxLayers = 15;

  config = {
    Cmd = [ "/bin/my-app" ];
    ExposedPorts = { "8080/tcp" = {}; };
    User = "nobody";  # Non-root user
  };
};

# Method 2: buildImage — single layer, lebih kecil
packages.dockerImageSmall = pkgs.dockerTools.buildImage {
  name = "my-app-minimal";
  tag = "latest";
  copyToRoot = pkgs.buildEnv {
    name = "image-root";
    paths = [ self.packages.${system}.default pkgs.cacert ];
  };
  config = {
    Cmd = [ "/bin/my-app" ];
  };
};

# Method 3: streamLayeredImage — stream langsung ke docker
# Tidak menyimpan file ke disk
packages.dockerStream = pkgs.dockerTools.streamLayeredImage {
  name = "my-app";
  tag = "latest";
  contents = [ self.packages.${system}.default ];
  config.Cmd = [ "/bin/my-app" ];
};

# Build & load ke Docker
# nix build .#dockerImage
# docker load < result

# Atau stream langsung
# nix build .#dockerStream | docker load

# Perbandingan ukuran image
# Nix layered image: ~30-50MB (Go app)
# Dockerfile (alpine): ~50-80MB
# Dockerfile (debian): ~150-300MB
💡 Mengapa Nix Container Lebih Kecil?

Nix secara otomatis hanya memasukkan runtime dependencies yang benar-benar diperlukan. Tidak ada base image (Alpine/Debian) yang menyertakan tools yang tidak perlu. Untuk aplikasi Go, image Nix bisa hanya 20-30MB karena hanya berisi binary dan minimal runtime dependencies.

6. devenv — Development Environments

devenv (powered by Nix) adalah tool untuk membuat development environments yang reproducible. Seperti Docker Compose untuk development, tapi lebih cepat dan native.

devenv.nix — Development environment
# Install devenv
nix profile install nixpkgs#devenv

# Init di project baru
devenv init

# devenv.nix
{ pkgs, ... }:
{
  # Packages yang tersedia di shell
  packages = [
    pkgs.git
    pkgs.ripgrep
    pkgs.jq
    pkgs.curl
  ];

  # Languages
  languages = {
    javascript = {
      enable = true;
      package = pkgs.nodejs_20;
      npm.enable = true;
    };
    typescript.enable = true;
    python = {
      enable = true;
      version = "3.12";
      venv.enable = true;
      venv.requirements = ./requirements.txt;
    };
    go.enable = true;
  };

  # Services (berjalan otomatis saat enter shell)
  services = {
    postgres = {
      enable = true;
      initialDatabases = [
        { name = "myapp"; }
      ];
      listen_addresses = "127.0.0.1";
      port = 5432;
    };
    redis.enable = true;
  };

  # Environment variables
  env = {
    DATABASE_URL = "postgresql://localhost:5432/myapp";
    REDIS_URL = "redis://localhost:6379";
    NODE_ENV = "development";
  };

  # Scripts yang bisa dijalankan
  scripts = {
    dev.exec = "npm run dev";
    test.exec = "npm test";
    lint.exec = "npm run lint";
    db-reset.exec = ''
      psql -h localhost -U postgres -c "DROP DATABASE IF EXISTS myapp;"
      psql -h localhost -U postgres -c "CREATE DATABASE myapp;"
    '';
  };

  # Pre-commit hooks
  pre-commit.hooks = {
    nixpkgs-fmt.enable = true;
    eslint.enable = true;
    prettier.enable = true;
  };

  # Enter shell
  enterShell = ''
    echo "🚀 Development environment ready!"
    echo "Node: $(node --version)"
    echo "Python: $(python --version)"
    echo ""
    echo "Commands: dev, test, lint, db-reset"
  '';
}

7. deploy-rs — Deployment

deploy-rs adalah tool untuk deploy NixOS configurations ke remote servers. Seperti Ansible/Terraform untuk NixOS, tapi lebih cepat dan atomic.

deploy-rs configuration
# Tambahkan deploy-rs ke flake inputs
inputs = {
  nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05";
  deploy-rs.url = "github:serokell/deploy-rs";
};

# Definisikan deployment targets
outputs = { self, nixpkgs, deploy-rs, ... }: {
  # NixOS configurations
  nixosConfigurations.web-server = nixpkgs.lib.nixosSystem {
    system = "x86_64-linux";
    modules = [
      ./hosts/web-server/configuration.nix
    ];
  };

  nixosConfigurations.db-server = nixpkgs.lib.nixosSystem {
    system = "x86_64-linux";
    modules = [
      ./hosts/db-server/configuration.nix
    ];
  };

  # Deploy configuration
  deploy.nodes = {
    web-server = {
      hostname = "web.example.com";
      sshUser = "deploy";
      # profiles default akan deploy system configuration
      profiles.system = {
        user = "root";
        path = deploy-rs.lib.x86_64-linux.activate.nixos
          self.nixosConfigurations.web-server;
      };
    };

    db-server = {
      hostname = "db.example.com";
      sshUser = "deploy";
      profiles.system = {
        user = "root";
        path = deploy-rs.lib.x86_64-linux.activate.nixos
          self.nosConfigurations.db-server;
        # Rollback timeout — auto rollback jika deploy gagal
        magicRollback = true;
      };
    };
  };
};

# Deploy commands
# Deploy semua nodes
deploy .

# Deploy specific node
deploy .#web-server

# Dry-run (hanya preview)
deploy .#web-server --dry-activate

# Rollback
ssh deploy@web.example.com "sudo /nix/var/nix/profiles/system-*-link/bin/switch-to-configuration switch"

8. NixOS Configuration

configuration.nix — NixOS server config
# hosts/web-server/configuration.nix
{ config, pkgs, ... }:
{
  # System basics
  system.stateVersion = "24.05";
  networking.hostName = "web-server";
  time.timeZone = "Asia/Jakarta";

  # Enable SSH
  services.openssh = {
    enable = true;
    settings = {
      PasswordAuthentication = false;
      PermitRootLogin = "no";
    };
  };

  # Firewall
  networking.firewall = {
    enable = true;
    allowedTCPPorts = [ 22 80 443 ];
  };

  # Nginx reverse proxy
  services.nginx = {
    enable = true;
    recommendedProxySettings = true;
    recommendedTlsSettings = true;

    virtualHosts."app.example.com" = {
      enableACME = true;
      forceSSL = true;
      locations."/" = {
        proxyPass = "http://127.0.0.1:8080";
        proxyWebsockets = true;
      };
    };
  };

  # Let's Encrypt
  security.acme = {
    acceptTerms = true;
    certs."app.example.com".email = "admin@example.com";
  };

  # Application service
  systemd.services.my-app = {
    description = "My Node.js Application";
    after = [ "network.target" ];
    wantedBy = [ "multi-user.target" ];
    serviceConfig = {
      ExecStart = "${pkgs.nodejs_20}/bin/node /opt/my-app/server.js";
      Restart = "always";
      User = "myapp";
      Group = "myapp";
      Environment = [
        "NODE_ENV=production"
        "PORT=8080"
      ];
    };
  };

  # Users
  users.users.myapp = {
    isSystemUser = true;
    group = "myapp";
  };
  users.groups.myapp = {};

  # Monitoring
  services.prometheus.exporters.node = {
    enable = true;
    port = 9100;
  };
}
⚠️ NixOS Learning Curve

NixOS memiliki learning curve yang cukup tinggi. Bahasa Nix berbeda dari bahasa pemrograman umum — bersifat fungsional, lazy evaluation, dan tidak banyak error messages yang jelas. Namun begitu dipahami, NixOS menawarkan tingkat reproducibility dan atomic rollback yang tidak ditawarkan oleh tool lain.

9. Quiz: Uji Pemahamanmu!

Setelah membaca tutorial di atas, jawablah 5 pertanyaan berikut:

Pertanyaan 1: Apa yang membuat Nix builds reproducible?

a) Menggunakan Docker containers
b) Setiap package di-build di isolated environment dengan hash unik berdasarkan semua input, sehingga hasilnya selalu sama
c) Menggunakan base image yang sama
d) Menggunakan lockfile seperti package-lock.json

Pertanyaan 2: Apa fungsi Nix Flakes?

a) Menggantikan Docker
b) Mendefinisikan Nix project dengan pinned dependencies secara eksplisit, menggantikan channel system
c) Membuat NixOS installation media
d) Mengelola Git repository

Pertanyaan 3: Mengapa Nix container images lebih kecil dari Dockerfile-based images?

a) Karena Nix menggunakan kompresi yang lebih baik
b) Karena Nix hanya memasukkan runtime dependencies yang benar-benar diperlukan tanpa base image
c) Karena Nix menghapus file yang tidak diperlukan
d) Karena Nix containers tidak mendukung multi-layer

Pertanyaan 4: Apa keunggulan deploy-rs dibandingkan SSH + script biasa?

a) deploy-rs lebih cepat dari SSH
b) deploy-rs mendukung atomic deployment dengan automatic rollback jika deploy gagal
c) deploy-rs menggantikan SSH sepenuhnya
d) deploy-rs hanya untuk NixOS desktop

Pertanyaan 5: Apa yang dimaksud dengan atomic upgrade di NixOS?

a) Upgrade yang membutuhkan reboot
b) Sistem beralih ke generasi baru secara instant — jika gagal, bisa rollback ke generasi sebelumnya tanpa downtime
c) Upgrade yang dilakukan secara otomatis
d) Upgrade yang hanya mempengaruhi satu package
← SebelumnyaGrafana Stack: Mimir + Tempo + Loki Selanjutnya →Chaos Engineering with Litmus
🔍 Zoom
100%
🎨 Tema