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.
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 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).
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
substitutersto point at the cache.After a successful build, push results into the cache with
nix copy --toorattic push.Subsequent builds are fast — only new derivations need building.
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:
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.
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 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.