Introducing Nix

Declarative builds and deployments

codgician

2024-05-20

Problems

  • Software should still work when distributed among machines.
  • Not the reality. Following challenges:
    • Environment issues
    • Managability issues

Environment issues

  • Components have dependencies.
  • Dependencies need to be compatible.
  • Dependencies should be discoverable.
  • Components may depend on non-software artifacts (e.g. configurations).

Manageability issues

  • Uninstall / Upgrade: should not induce failures to another part of the system (e.g. DLL hell).
  • Administrator queries: file ownership? disk space consumption? source?
  • Rollbacks: able to undo effects of upgrades.
  • Variability: build / deployment configurations may differ.
  • … and they scale for a huge fleet of machines with different SKUs.

Industry solutions?

Idea #1. Global package management

  • Systematically manage packages (e.g. apt, yum, pacman, etc).
  • Each component provide a set of constraints:
    • A is installed \rightarrow B (>= 1.0) must be installed.
    • A is installed \rightarrow C should NOT be installed.
    • Success deployment \rightarrow pkg1, pkg2, … is installed.
  • Solve: success deployment.

B-SAT problem (NP-complete).

::: { .fragment .fade-in } Implement pseudo-solvers. :::

When you try to manage the global system,

you lose isolation.

  • Two components want different versions of the same dependency?
  • Two components providing files on the same location?
  • Upgrading is destructive, and not atomic.
  • Files are usually overwritten, making rollbacks non-trivial.

Idea #2. Go monolithic!

  • Environment issues?
    • Resolving undeterministic dependency is hard.
    • Why not bundle everything?
  • Managability issues?
    • Isolation between bundles.
    • Only load dependencies which are bundled inside.
  • Self-contained packaging: AppImage, most Windows/macOS apps, etc.
  • Containers, and virtualization based technologies.

Wait, won’t build + deployment be complex due to monolithic?

::: { .fragment } Chunking. :::

  • Break big software into multiple parts.
    • Inside each part, we go monolithic.
    • Between parts, apply simpler dependency management. :::
  • Break complex build and deployment into multiple stages.
    • Inside each stage, we go imperative.
    • Between xstages, we declaratively define dependencies. :::
  • Break huge docker image into multiple layers.
    • Inside each layer, we go imperative.
    • Between layers, apply simpler dependency management. ::: :::