@@ -5,10 +5,10 @@ A Nix flake that builds an **OCI image** (via
55Apple's [`container`](https://github.com/apple/container) CLI on macOS.
66
77It's a plain, single-process image — no NixOS, no systemd — defined in
8−[`flake.nix`](./flake.nix). `c start` drops you into a
9−[`herdr`](https://herdr.dev) session whose panes use your host login shell
10−(detected and bundled by `c init`). You choose what else it ships via a
11−per-project `container.nix`.
8+[`flake.nix`](./flake.nix). The default command is `fish` (with a configured
9+prompt), and the image ships a handful of dev/LLM tools: `git`, `jujutsu`,
10+`ripgrep`, `fd`, `jq`, `nodejs`, `python3`, a Rust + C toolchain,
11+`charmbracelet.crush`, `openssh`, and more.
1212
1313## Prerequisites
1414
@@ -16,72 +16,36 @@ per-project `container.nix`.
1616- Apple's [`container`](https://github.com/apple/container) CLI installed, with
1717 services started: `container system start` (first run downloads a Linux
1818 kernel).
19−- A **Linux builder** *only if you use packages that aren't in the binary
20− cache*. The image is assembled entirely on macOS — the root filesystem is just
21− a host-built symlink tree over the **substituted** Linux store paths — so a
22− fully-cached package set needs no builder. Compiling a Linux package that
23− `cache.nixos.org` doesn't have does, though: use a [remote builder][builders]
24− or, on Apple Silicon, the `nix-darwin` `linux-builder`.
19+- A **Linux builder** — the image *contents* are Linux binaries, which can't be
20+ built on macOS. Use a [remote builder][builders] or, on Apple Silicon, the
21+ `nix-darwin` `linux-builder`. With one configured, `nix` dispatches Linux
22+ builds to it automatically (most packages also come prebuilt from the binary
23+ cache, so the builder is only needed for the uncached bits).
2524
2625[builders]: https://nix.dev/manual/nix/latest/advanced-topics/distributed-builds
2726
28−## Packages
29−
30−`init` builds the image from a **`container.nix`** in the current directory — an
31−attrset with a `packages` function (`{ pkgs, nur }` returning the package list;
32−`nur` is [NUR](https://github.com/nix-community/NUR), for packages outside
33−nixpkgs) and an optional `mounts` list. It is **required**; there is no default.
34−
35−```nix
36−# ./container.nix
37−{
38− packages = { pkgs, nur }: with pkgs; [
39− git
40− go
41− nur.repos.charmbracelet.crush
42− ];
43− # Extra "host:container" bind mounts, on top of $PWD → /workspace. A leading
44− # ~/ in the host path expands to $HOME. Optional.
45− mounts = [
46− "~/data:/data"
47− ];
48−}
49−```
50−
51−Your **host login shell** (detected by `c init`, bash fallback), **coreutils**,
52−[**herdr**](https://herdr.dev) (the session `c start` launches), **Nix** itself
53−(with CA certs and `nix-command`/`flakes` enabled, single-user), and the ncurses
54−**terminfo** database are always included — no need to list them.
55−
56−Point at a different file with `-c`/`--config`:
57−
58−```sh
59−nix run github:ilyakooo0/nix-container -- init --config envs/ci.nix
60−```
61−
6227## Quick start
6328
64−No clone needed — `nix run` drives everything. Run it from a project directory
65−that holds a `container.nix`; the container is named after that directory.
29+No clone needed — `nix run` drives everything. Run it from a project directory;
30+the container is named after that directory.
6631
6732```sh
6833# build the image, load it, and create the container (named after $PWD):
6934nix run github:ilyakooo0/nix-container -- init
7035
71−# start it and attach — drops you into a herdr session:
36+# start it and attach — drops you into fish:
7237nix run github:ilyakooo0/nix-container -- start
7338```
7439
7540- `init` builds the OCI archive in a temp dir, loads it into `container`,
76− deletes the temp file, then (re)creates the container. It mounts the current
77− directory at `/workspace` (the container's working directory).
41+ deletes the temp file, then runs `container create --name <dir> --ssh -it`.
7842- `--ssh` forwards your host SSH agent socket into the container (so `git` over
7943 SSH works with your keys).
80−- Add more mounts persistently via `mounts` in `container.nix` (above), or
81− ad-hoc by passing them through to `create` (mounts can only be set at creation):
44+- Mounts can only be set at creation, so pass them to `init` (forwarded to
45+ `container create`):
8246
8347 ```sh
84− nix run github:ilyakooo0/nix-container -- init -v $HOME/data:/data
48+ nix run github:ilyakooo0/nix-container -- init -v $PWD:/work
8549 ```
8650
8751Working from a checkout, [`./c init`](./c) / `./c start` are equivalent — and
@@ -89,29 +53,75 @@ Working from a checkout, [`./c init`](./c) / `./c start` are equivalent — and
8953
9054## Re-running after a rebuild
9155
92−Just run `init` again — it reloads the image and replaces the existing container
93−(removing the old one first), so there's nothing to clean up:
56+`container` keeps the loaded image and the created container, so remove them
57+before re-initialising:
9458
9559```sh
60+container rm <dir> # the container is named after the directory
61+container image rm nix-container:latest
9662nix run github:ilyakooo0/nix-container -- init
9763```
9864
99−## Customize further
65+## Manual steps
66+
67+`init` is just a wrapper; you can run the pieces yourself for more control. The
68+`image` app is nix2container's skopeo wrapper (`skopeo copy nix:<image> "$@"`) —
69+it takes the ref name from the destination, so spell out the tag:
10070
101−Fork or clone to change what isn't per-project — `config.cmd`/`env` or the image
102−name — in [`flake.nix`](./flake.nix). The flake also exposes build helpers:
71+```sh
72+# 1. build a tagged OCI archive
73+nix run github:ilyakooo0/nix-container#image -- oci-archive:image.tar:nix-container:latest
10374
104−- `lib.<system>.copyWith` → a skopeo copy app for a `{ pkgs, nur }: [ ... ]`
105− function, for building/pushing the image anywhere skopeo can write (e.g. a
106− registry):
75+# 2. load, create, start
76+container image load --input image.tar
77+container create --name myctr --ssh -it nix-container:latest
78+container start -ai myctr
79+```
10780
108− ```sh
109− drv=$(nix build --no-link --print-out-paths --impure --expr \
110− '(builtins.getFlake "github:ilyakooo0/nix-container").lib.${builtins.currentSystem}.copyWith (import ./container.nix).packages')
111− $drv/bin/copy-to docker://ghcr.io/me/img:latest
112− ```
113−- `lib.<system>.mkImage <name> <shell>` → the underlying OCI image (`copyWith`
114− defaults these to `nix-container` / `bash`).
81+## Other image destinations
82+
83+The same `image` app can copy anywhere skopeo can:
84+
85+```sh
86+nix run github:ilyakooo0/nix-container#image -- docker://ghcr.io/me/img:latest # push to a registry
87+nix run github:ilyakooo0/nix-container#image -- docker-daemon:nix-container:latest # local Docker daemon
88+```
89+
90+## Configuring packages
91+
92+The image ships a curated tool set (`defaultPackages` in
93+[`flake.nix`](./flake.nix)). To use your own set **without forking**, drop a
94+`container.nix` in the directory you run `init` from — a `{ pkgs, nur }`
95+function returning the package list (`nur` is [NUR](https://github.com/nix-community/NUR),
96+for packages outside nixpkgs):
97+
98+```nix
99+# ./container.nix
100+{ pkgs, nur }: with pkgs; [
101+ fish # the default cmd is /bin/fish, so include it (or change config.cmd)
102+ coreutils
103+ go
104+ terraform
105+ nur.repos.charmbracelet.crush
106+]
107+```
108+
109+`nix run github:ilyakooo0/nix-container -- init` (and `./c init`) then builds an
110+image with **exactly** those packages, replacing the default set. It's a full
111+replacement, so include the basics you need.
112+
113+Point at a different file with `-c`/`--config`:
114+
115+```sh
116+nix run github:ilyakooo0/nix-container -- init --config envs/ci.nix
117+```
118+
119+## Customize further
120+
121+Fork or clone to change what isn't per-project — the default package set,
122+`config.cmd`/`env`, or the image name — by editing `defaultPackages` / `mkImage`
123+in [`flake.nix`](./flake.nix). `mkImage` and `lib.<system>.copyWith` are exposed
124+for building images from your own flake.
115125
116126The flake uses `flake-utils` over `{aarch64,x86_64}-{darwin,linux}`; the host
117127system maps to the matching Linux target automatically (an Intel Mac builds an
@@ -119,12 +129,9 @@ system maps to the matching Linux target automatically (an Intel Mac builds an
119129
120130## How it works
121131
122−- **Contents** are Linux store paths — downloaded prebuilt from the binary cache
123− (only built on a Linux builder if not cached).
124−- **Assembly** runs entirely on your Mac: `copyToRoot` is a *host-arch* `buildEnv`
125− symlink tree over those Linux paths (symlinks are arch-neutral), and
126− `nix2container` + `skopeo` package it — so for a cached set, `init` produces a
127− loadable Linux image with no Linux build at all.
132+- **Contents** (`copyToRoot`) are built from `pkgsLinux` → the Linux builder.
133+- **Assembly** (`nix2container` + the `skopeo` copy) runs on your Mac, so a
134+ single `nix run` produces a loadable archive.
128135- Apple's `container` runs each container in its own lightweight Linux VM and
129136 consumes standard OCI images — no `--privileged` or systemd needed.
130137