# Nixery builds container images from their own names

> Nixery is a Docker-compatible registry that assembles an image the moment someone pulls it, reading package names straight out of the image path as nixpkgs keys. The resolution mechanism is small and legible; the operational details around build timeouts, private credentials and where the code really lives are not.

**tazjin/nixery** — Container registry which transparently builds images using the Nix package manager. Canonical repository is https://cs.tvl.fyi/depot/-/tree/tools/nixery

- Repository: https://github.com/tazjin/nixery
- Website: https://nixery.dev/
- Stars: 2,024 · Forks: 81
- Language: Go
- License: Apache-2.0
- Published: 2026-10-14 · Updated: 2026-10-14 · Language: en
- Canonical page: https://hysenlabs.com/projects/tazjin-nixery

## Every image path component becomes an nixpkgs key

Nixery sits where a container registry normally only stores bytes. When a client pulls a name, that name is read as the build specification: each path component is resolved as a top-level key in `nixpkgs`, and the image is assembled from those keys on the spot. No build context travels with the pull, and nothing about the image is declared in advance.

That shape carries one ceiling and one convenience. The ceiling is that whatever you can pull is bounded by what the configured package set exposes, because a path component that is not a top-level key means nothing to the resolver. The convenience is that an image description is a short string that fits in a CI variable or a deployment manifest, so a team can pin a toolchain without keeping a build pipeline alive for it.

Choosing that package set happens through the environment, not through a file inside the image. `NIXERY_CHANNEL` names a Nix/NixOS channel for the public case, and the public instance at `nixery.dev` fixes that choice for everyone pulling from it. Self-hosted instances point elsewhere, which is what the private set mechanism covers.

## Layer order follows a popularity file fetched over HTTP

Resolving names is only half of the work. Nixery then places the resulting store paths into image layers with the layering strategy described in the buildLayeredImage blog post the project grew out of, and the stated goal of that strategy is to optimise caching for popular and/or large dependencies.

Ordering input arrives from outside the process. `NIX_POPULARITY_URL` names a URL holding popularity data for the package set, and a matching `popcount/` directory sits in the repository tree, so the component that decides which dependencies share a layer is fed from a file the operator hosts. No default value or bundled list is given for that variable, and an unset value leaves the ordering open rather than resolved.

A second factor sits in the package set itself. The special meta-package `shell` provides an image base with many core components, such as `bash` and `coreutils`, that people expect in interactive images. That is how the recorded demo ends up with an interactive shell and GNU `hello` inside one pulled image. A NixCon 2019 talk linked from the repository covers the project and its use cases for anyone who wants the longer version.

## Running an instance from the TVL depot subtree

The canonical location of the Nixery source code is `//tools/nixery` in the TVL monorepository, and a subtree clone is offered for people who do not want the whole depot. That clone is the real first step for anyone running their own registry:

```bash
git clone https://code.tvl.fyi/depot.git:/tools/nixery.git
```

What lands on disk is laid out along the stages of the service. `cmd/` and `config/` hold the Go process and its settings, `builder/` and `layers/` cover the image work, `storage/` holds the backends, and `popcount/` belongs to the popularity data. `manifest/` and `prepare-image/` carry names that match the manifest and preparation steps of serving an image, though nothing spells out what lives in them. `default.nix`, `shell.nix` and `scripts/` are the Nix-side entry points for working on the code.

Configuration is environment variables only, so there is no config file to edit after the clone. Set `PORT` for the listener, `NIXERY_CHANNEL` for the package source, and `NIXERY_STORAGE_BACKEND` for where layers live, where the accepted values are gcs, filesystem and s3. Each backend then needs its own key: `STORAGE_PATH` for the local disk, `GCS_BUCKET` for Google Cloud Storage, `S3_BUCKET` for S3. Starting with the filesystem backend puts build output on local disk and lets you see what a build leaves behind before object storage is involved.

What the documentation does not give you is a packaged binary or container image for Nixery itself. No build command for the Go binary appears there, so the deployment path is the depot subtree plus whatever tooling you already run for Go services.

## Private package sets turn image tags into git references

A public instance can only serve what a public channel carries. For an organisation with its own packages, `NIXERY_PKGS_REPO` points at a git repository containing a package set and `NIXERY_PKGS_PATH` points at a local filesystem path holding one, so the same image-name grammar resolves against a private catalogue instead.

Tags in the pull are forwarded as git references. If a company overlays its packages on the Nix package set in a custom repository, an image is requested from a git tag like this:

```bash
docker pull nixery.thecompany.website/custom-service:release-v2
```

Access to that private set rests on the host, not on the request. The `NIXERY_PKGS_REPO` description says it uses locally configured SSH/git credentials, so the repository has to be reachable with the credentials the registry host already holds, and the tag is passed straight through rather than authorised per pull.

The registry side has no equivalent knob. No variable in the configuration list covers user accounts or push credentials, and every flow described here ends at building and serving, so the private package set feature fits an instance inside a trusted network rather than an open one.

## Layer serving changes with the bucket credential

After a build, every layer goes into the storage bucket and later requests for those layers are forwarded to it, which is also what lets redundant instances share layers. How that forwarding happens differs per backend, and one difference has direct security consequences.

With the gcs backend, clients are redirected to the bucket. Set `GOOGLE_APPLICATION_CREDENTIALS` to a service account key and Nixery also uses it to create signed URLs, so the bucket objects do not need to be public. Leave it unset and a redirect to `storage.googleapis.com` is issued, which means the underlying bucket objects have to be publicly accessible. One environment variable flips the same deployment between a private bucket and an open one.

S3 carries no such branch: layers are served by redirecting clients to the S3 bucket with signed URLs. `S3_ENDPOINT` covers non-AWS providers, `S3_ACCESS_KEY` and `S3_SECRET_KEY` are optional because IAM roles are used when they are unset, and `AWS_REGION` falls back to `us-east-1` when omitted, which works with most non-AWS S3 providers. Under the filesystem backend, layers are served straight from the local disk at `STORAGE_PATH`, with no bucket and no signature in the path.

## Nix builders get 60 seconds by default

`NIX_TIMEOUT` is the number of seconds any Nix builder is allowed to run, and it defaults to 60. That is a tight budget for a cold build of a large package set, and it pulls against the caching story: layers are ordered to make popular and large dependencies cheap to reuse, yet a build that must compile from scratch has one minute before something has to give.

What a client sees when a build runs past that limit is not written down. The configuration section has no queue depth, no retry count and no documented error response, so behaviour under load has to be discovered rather than planned for.

Two smaller gaps sit beside it. `NIXERY_PKGS_REPO` and `NIXERY_PKGS_PATH` are listed as separate ways to supply a package set, with no precedence given for the case where both are set. The variable naming also mixes prefixes: most settings start with `NIXERY_`, while `NIX_TIMEOUT` and `NIX_POPULARITY_URL` carry a bare `NIX_` prefix, which matters when a deployment manifest is generated from a pattern rather than copied by hand.

## The GitHub repository is an infrequent subtree mirror

The tree you browse on GitHub is not where the work happens. The README states the subtree is infrequently mirrored to `tazjin/nixery` on Github, while the canonical location of the source is `//tools/nixery` in the TVL monorepository, and that is also where the open Kubernetes integration item, issue #4, lives. The last push recorded for the mirror is 2026-04-08, and the repository has no GitHub releases to read.

A marker file at the top of the tree, `.skip-subtree`, belongs to that mirroring arrangement, so the GitHub copy sits downstream of a sync rather than acting as the source of record.

The Go module path shows the same drift from an older home. `go.mod` declares `module github.com/google/nixery`, which matches neither the repository name nor the depot subtree, and it asks for Go 1.24:

```go
module github.com/google/nixery
go 1.24
```

Its direct dependencies say what the service leans on: cloud.google.com/go/storage v1.22.1, github.com/aws/aws-sdk-go-v2 v1.41.3 and github.com/aws/aws-sdk-go-v2/config v1.32.11 for the two object stores, plus github.com/pkg/xattr v0.4.12, gonum.org/v1/gonum v0.16.0 and github.com/im7mortal/kmutex v1.0.2. A numerical library carrying that weight in a service whose visible job is mostly name resolution is worth opening in the source, since nothing in the documentation explains what the numeric and attribute work covers.

## The alternative is a buildLayeredImage derivation per image

The project started out inspired by the buildLayeredImage blog post, and that approach remains the honest comparison point. With a derivation you write the layer layout yourself, evaluate it inside your own Nix store, and rebuild when the package set moves. The image becomes an artifact your configuration can pin, diff and cache.

Nixery inverts that arrangement. Nothing is evaluated where you work: the registry host resolves the name against its channel or private set, runs the build inside the timeout, and leaves the result in a bucket that clients are redirected to. You gain a pullable URL with no build step in front of it, and you give up the ability to read or version the expression that produced the bytes.

The roadmap sits between the two. One item describes deploying Nixery inside a Kubernetes cluster with correct caching behaviour and addressing, and the background notes that the original design aimed at a Kubernetes controller serving declarative image specifications written in CRDs, outlined in a public gist. The Nix-native builder item would move image building and layering into a separate Nix function so images can be built directly in Nix builds, which lands much closer to the derivation model with a registry in front of it.

## Conclusion

Nixery earns a place in setups where Nix is already the source of truth and pulling a name beats maintaining image expressions; elsewhere the channel choice, the build host and the 60 second `NIX_TIMEOUT` arrive as inherited constraints rather than decisions anyone made. Verify which copy you are running before anything else: the canonical code is `//tools/nixery` in the TVL depot, the GitHub tree is an infrequently mirrored subtree, and `go.mod` still names `github.com/google/nixery`. Set `NIX_POPULARITY_URL` before anything goes live, since the layer order it feeds has no documented default.

## FAQ

### Where is the canonical Nixery source code?

The canonical location is `//tools/nixery` in the TVL monorepository, reachable as a subtree clone. The GitHub repository `tazjin/nixery` is an infrequently mirrored copy of that subtree, and the Go module path in `go.mod` still names `github.com/google/nixery`.

### How do I run my own Nixery instance instead of using nixery.dev?

Clone the subtree from the TVL depot and configure it entirely through environment variables, starting with `PORT`, `NIXERY_CHANNEL` and `NIXERY_STORAGE_BACKEND`. The documentation gives no build command for a Go binary and no packaged container image for Nixery itself, so deployment uses the tooling you already run for Go services.

### Can Nixery serve packages that are not in a public Nix channel?

Yes, a private instance can build from a package set held in a custom git repository via `NIXERY_PKGS_REPO` or on a local filesystem path via `NIXERY_PKGS_PATH`. With git repositories the image tags from the pull are forwarded as git references, and the repository is reached with the locally configured SSH/git credentials.

### How long is a Nixery build allowed to run?

`NIX_TIMEOUT` sets the number of seconds any Nix builder may run and defaults to 60. The configuration documents no queue depth, retry count or error response for builds that exceed the limit.

### Do I need a public storage bucket for Nixery image layers?

With the gcs backend it depends on `GOOGLE_APPLICATION_CREDENTIALS`: set to a service account key, Nixery creates signed URLs, and unset, clients get a redirect to `storage.googleapis.com` that requires publicly accessible objects. S3 layers are always served with signed URLs, and filesystem layers are served from local disk.

### How do I get an image with a working shell in it?

Use the special meta-package `shell` as a path component, which provides an image base with many core components such as `bash` and `coreutils` for interactive images. Every other package in the image is added the same way, as another path component of the image name.

## Sources

- [Issues](https://github.com/tazjin/nixery/issues)
- [License: Apache-2.0](https://github.com/tazjin/nixery/blob/master/LICENSE)
- [Project website](https://nixery.dev/)
- [README](https://github.com/tazjin/nixery/blob/master/README.md)
- [tazjin/nixery on GitHub](https://github.com/tazjin/nixery)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tazjin-nixery
