.. _arch-nix-builds:

==========
Nix Builds
==========

Goal
====

Build Nix-based container images via MyCI and push them to a forge
container registry. Support both ``myci run`` (local) and
server-triggered builds (webhook from Forgejo).


Two Execution Paths
===================

``myci run`` selects its executor — it is not tied to host
execution. ``myci run --executor host`` runs steps as local shell
commands where Nix is already available. ``myci run --executor k8s``
runs steps in Kubernetes pods where Nix must be provided.

The same workflow should work on both paths. The difference is the
execution environment, not the workflow definition.


Incremental Slices
==================

Slice 1: Host executor (works today)
-------------------------------------

The host executor runs steps as local shell commands. Nix is
available on the developer's machine. No MyCI changes needed.

.. code-block:: yaml

    jobs:
      build:
        steps:
          - uses: actions/checkout@v4
          - name: Build image
            run: nix build .#server-image
          - name: Push to registry
            run: |
              ./result | skopeo copy \
                docker-archive:/dev/stdin \
                docker://code.example/myci/server:latest

For local use without a registry, ``./result | podman load`` loads
the image into the local container runtime.

**Limitation**: requires credentials for registry push. See
:ref:`arch-secrets-and-credentials`.

Slice 2: Kubernetes with Nix container
--------------------------------------

Use ``container: nixos/nix`` in the workflow. The ``nixos/nix``
image provides Nix in an unprivileged container. Nix sandboxing
must be disabled (no nested namespaces in k8s pods).

.. code-block:: yaml

    jobs:
      build:
        container: nixos/nix:latest
        steps:
          - uses: actions/checkout@v4
          - name: Configure Nix
            run: |
              echo "sandbox = false" >> /etc/nix/nix.conf
              echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
          - name: Build image
            run: nix build .#server-image

**Limitation**: every run starts with an empty Nix store. A
``nix build`` of a flake downloads nixpkgs and all dependencies
from scratch — easily 10+ minutes of fetching before the actual
build starts. This is impractical without caching.

Slice 3: Binary cache
---------------------

A Nix binary cache is an HTTP service that serves pre-built store
paths. Builds fetch from the cache instead of rebuilding. The cache
is read-heavy, write-rare, and designed for concurrent access.

Infrastructure needed:

- A cache service (attic, harmonia, or similar) deployed in the
  cluster or accessible from it.
- Workflow steps configure ``substituters`` to point at the cache.
- After a successful build, push results into the cache with
  ``nix copy --to`` or ``attic push``.
- Subsequent builds are fast — only new derivations need building.

.. code-block:: yaml

    jobs:
      build:
        container: nixos/nix:latest
        steps:
          - uses: actions/checkout@v4
          - name: Configure Nix
            run: |
              echo "sandbox = false" >> /etc/nix/nix.conf
              echo "experimental-features = nix-command flakes" >> /etc/nix/nix.conf
              echo "extra-substituters = http://nix-cache.example" >> /etc/nix/nix.conf
              echo "extra-trusted-public-keys = cache.example:..." >> /etc/nix/nix.conf
          - name: Build image
            run: nix build .#server-image
          - name: Push to cache
            run: nix copy --to http://nix-cache.example ./result

This makes k8s Nix builds practical for iterative development.

Slice 4: Native Nix environment (future)
-----------------------------------------

Jobs could specify a Nix shell instead of a container image:

.. code-block:: yaml

    jobs:
      build:
        nix: .#devShells.default
        steps:
          - run: cargo build

The executor evaluates the flake attribute and provides the
resulting environment. On k8s, this could be an init container
that runs ``nix build`` and mounts the result. On the host
executor, it would be ``nix develop`` wrapping the shell commands.

This is a longer-term direction. The container-based approach
(slices 1-3) covers the immediate need.


Why Not a Shared Nix Store Volume
=================================

A persistent volume mounted at ``/nix/store`` across pods seems
like a simpler alternative to a binary cache. It is not.

The Nix daemon uses a SQLite database at ``/nix/var/nix/db/`` to
track store paths. SQLite on shared filesystems (NFS, most
``ReadWriteMany`` providers) is a known source of corruption. Two
concurrent ``nix build`` runs writing to the same database will
corrupt it — even though the store paths themselves are
content-addressed and safe to share.

A binary cache is the correct shared caching mechanism for Nix. It
is HTTP-based, designed for concurrent access, and separates read
(substituter) from write (push) concerns cleanly.


Artifacts
=========

Registry as artifact store
--------------------------

For container images, the registry is the artifact store. No
separate artifact mechanism is needed — the workflow pushes to the
registry, consumers pull from it. The image tag (or digest) is the
artifact identifier.

Local artifacts
---------------

When running locally without a forge or registry, the workflow
handles output directly:

- ``./result | podman load`` — loads into local container runtime.
- ``cp ./result /some/path`` — copies the build output.
- ``nix copy --to /some/cache`` — pushes to a local cache.

A formal artifact system (upload/download between jobs, persistent
artifact storage) is a separate concern. It is not needed for the
Nix image build use case.


Connection to Worker Split
==========================

The :ref:`worker/agent split <arch-worker-agent-split>` enables a
pragmatic intermediate path: a host-based worker with Nix installed,
connected to the MyCI server.

This gives server-triggered Nix builds (webhook → server → worker
→ host executor → ``nix build``) without the overhead of running
Nix inside k8s containers. The host has a warm Nix store, fast
builds, and direct access to local infrastructure.

This is the recommended path while k8s Nix support (slices 2-3)
matures. Once a binary cache is deployed and the container approach
is proven, k8s workers can take over.
