on master help sign in
Support per-project package sets via ./nix-container.nix
commit0937aa769a53d5f5c068431f0582acecabdb9e2c authoriko <mail@iko.soy> dateSat 27 Jun 2026, 21:01 (3 months ago) parent443319f
3 files changed+142−76
README.md+25−5 c+25−2 flake.nix+92−69
README.md+25−5
@@ -87,13 +87,33 @@ nix run github:ilyakooo0/nix-container#image -- docker://ghcr.io/me/img:latest
8787nix run github:ilyakooo0/nix-container#image -- docker-daemon:nix-container:latest # local Docker daemon
8888```
8989
90−## Customize
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+`nix-container.nix` in the directory you run `init` from — a `pkgs`-function
95+returning the package list:
96+
97+```nix
98+# ./nix-container.nix
99+pkgs: with pkgs; [
100+ fish # the default cmd is /bin/fish, so include it (or change config.cmd)
101+ coreutils
102+ go
103+ terraform
104+]
105+```
106+
107+`nix run github:ilyakooo0/nix-container -- init` (and `./c init`) then builds an
108+image with **exactly** those packages, replacing the default set. It's a full
109+replacement, so include the basics you need.
91110
92−Fork or clone, then edit the `buildImage` call in [`flake.nix`](./flake.nix):
111+## Customize further
93112
94−- Add packages to `copyToRoot.paths`.
95−- Change the default process via `config.cmd`.
96−- Add env vars via `config.env`.
113+Fork or clone to change what isn't per-project — the default package set,
114+`config.cmd`/`env`, or the image name — by editing `defaultPackages` / `mkImage`
115+in [`flake.nix`](./flake.nix). `mkImage` and `lib.<system>.copyWith` are exposed
116+for building images from your own flake.
97117
98118The flake uses `flake-utils` over `{aarch64,x86_64}-{darwin,linux}`; the host
99119system maps to the matching Linux target automatically (an Intel Mac builds an
c+25−2
@@ -1,6 +1,7 @@
11#!/usr/bin/env fish
22# Manage a container named after the current directory.
33# c init [extra create args] build + load the image, then create the container
4+# (uses ./nix-container.nix for packages if present)
45# c start start it and attach
56
67set -l name (path basename $PWD)
@@ -11,10 +12,32 @@ switch $argv[1]
1112 case init
1213 # Build the OCI archive into a temp dir, load it, then clean up.
1314 set -l tmp (mktemp -d)
14− nix run "$flake#image" -- "oci-archive:$tmp/image.tar.gz:nix-container:latest"; and container image load --input $tmp/image.tar.gz
15− set -l rc $status
15+ set -l archive $tmp/image.tar.gz
16+ set -l rc 0
17+
18+ if test -e nix-container.nix
19+ # Per-project package set: build an image with exactly these packages.
20+ set -lx NIX_CONTAINER_PKGS (path resolve nix-container.nix)
21+ set -l expr "(builtins.getFlake \"$flake\").lib.\${builtins.currentSystem}.copyWith (import (/. + builtins.getEnv \"NIX_CONTAINER_PKGS\"))"
22+ set -l copyer (nix build --impure --no-link --print-out-paths --expr "$expr")
23+ set rc $status
24+ if test $rc -eq 0
25+ $copyer/bin/copy-to "oci-archive:$archive:nix-container:latest"
26+ set rc $status
27+ end
28+ else
29+ nix run "$flake#image" -- "oci-archive:$archive:nix-container:latest"
30+ set rc $status
31+ end
32+
33+ if test $rc -eq 0
34+ container image load --input $archive
35+ set rc $status
36+ end
37+
1638 rm -rf $tmp
1739 test $rc -eq 0; or exit $rc
40+
1841 container create --name $name --ssh -it $argv[2..-1] nix-container:latest
1942 case start
2043 container start -ai $name
flake.nix+92−69
@@ -110,77 +110,89 @@
110110 cp ${fishConfig} "$out/root/.config/fish/config.fish"
111111 '';
112112
113− image = n2c.buildImage {
114− name = imageName;
115− tag = imageTag;
116− inherit arch;
117−
118− # A plain, single-process root filesystem. No NixOS, no systemd.
119− copyToRoot = pkgsLinux.buildEnv {
120− name = "root";
121− paths = with pkgsLinux; [
122− bashInteractive
123− fish
124− fishRoot
125− openssh
126− coreutils-full
127− btop
128− jujutsu
129− zellij
130− micro
131− helix
132− nurPkgs.repos.charmbracelet.crush
133−
134− # LLM tools
135− git
136− ripgrep
137− fd
138− jq
139− curl
140− wget
141− gnused
142− gnugrep
143− gawk
144− findutils
145− gnutar
146− gzip
147− unzip
148− less
149− which
150− tree
151− nodejs
152− python3
153−
154− # rust
155− rustc
156− cargo
157− rustfmt
158− clippy
159−
160− # C toolchain — rustc links final binaries through `cc`/`ld`.
161− gcc # provides cc/gcc; pulls in binutils (ld) via its closure
162− ];
163− pathsToLink = [
164− "/bin"
165− "/root"
166− ];
113+ # The curated tool set baked into the image. Drop a `nix-container.nix`
114+ # (`pkgs: [ ... ]`) next to where you run `c init` to replace this with
115+ # your own list — see README.
116+ defaultPackages =
117+ pkgs: with pkgs; [
118+ bashInteractive
119+ fish
120+ openssh
121+ coreutils-full
122+ btop
123+ jujutsu
124+ zellij
125+ micro
126+ helix
127+ nurPkgs.repos.charmbracelet.crush
128+
129+ # LLM tools
130+ git
131+ ripgrep
132+ fd
133+ jq
134+ curl
135+ wget
136+ gnused
137+ gnugrep
138+ gawk
139+ findutils
140+ gnutar
141+ gzip
142+ unzip
143+ less
144+ which
145+ tree
146+ nodejs
147+ python3
148+
149+ # rust
150+ rustc
151+ cargo
152+ rustfmt
153+ clippy
154+
155+ # C toolchain — rustc links final binaries through `cc`/`ld`.
156+ gcc # provides cc/gcc; pulls in binutils (ld) via its closure
157+ ];
158+
159+ # Build the OCI image from a `pkgs: [ ... ]` package function. The fish
160+ # prompt config is always added; the default `cmd` is /bin/fish, so a
161+ # custom package set should include `fish` (or change `config.cmd`).
162+ mkImage =
163+ packages:
164+ n2c.buildImage {
165+ name = imageName;
166+ tag = imageTag;
167+ inherit arch;
168+
169+ # A plain, single-process root filesystem. No NixOS, no systemd.
170+ copyToRoot = pkgsLinux.buildEnv {
171+ name = "root";
172+ paths = packages pkgsLinux ++ [ fishRoot ];
173+ pathsToLink = [
174+ "/bin"
175+ "/root"
176+ ];
177+ };
178+
179+ initializeNixDatabase = true;
180+
181+ config = {
182+ # `cmd` (not `entrypoint`): it's the default command and is
183+ # *replaced* by `container run … -- <cmd>`. An entrypoint would
184+ # be prepended instead, so `run … -- /bin/sh` would become
185+ # `/bin/bash /bin/sh` and fail with "cannot execute binary file".
186+ cmd = [ "/bin/fish" ];
187+ env = [
188+ "PATH=/bin"
189+ # fish reads its prompt from $HOME/.config/fish/config.fish.
190+ "HOME=/root"
191+ ];
192+ };
167193 };
168194
169− initializeNixDatabase = true;
170−
171− config = {
172− # `cmd` (not `entrypoint`): it's the default command and is
173− # *replaced* by `container run … -- <cmd>`. An entrypoint would be
174− # prepended instead, so `run … -- /bin/sh` would become
175− # `/bin/bash /bin/sh` and fail with "cannot execute binary file".
176− cmd = [ "/bin/fish" ];
177− env = [
178− "PATH=/bin"
179− # fish reads its prompt from $HOME/.config/fish/config.fish.
180− "HOME=/root"
181− ];
182− };
183− };
195+ image = mkImage defaultPackages;
184196
185197 # `nix run . -- init|start` runs the bundled `c` manager script. Its
186198 # build step shells back to `nix run .#image`; the script finds the
@@ -202,6 +214,17 @@
202214 # the destination, so spell out the tag to avoid an `untagged` load.
203215 image = flake-utils.lib.mkApp { drv = image.copyTo; };
204216 };
217+
218+ # Build helpers. `c init` uses `copyWith` to honour a per-project
219+ # `nix-container.nix`:
220+ # (builtins.getFlake "github:…/nix-container")
221+ # .lib.<system>.copyWith (import ./nix-container.nix)
222+ lib = {
223+ inherit mkImage;
224+ # skopeo copy app (`skopeo copy nix:<image> "$@"`) for an image built
225+ # from a `pkgs: [ ... ]` function.
226+ copyWith = packages: (mkImage packages).copyTo;
227+ };
205228 }
206229 );
207230}