Containerized multi-user Nix development environments for platforms where NixOS cannot be used directly.
Four container variants ship prebuilt multi-arch images (x86_64, aarch64) to ghcr.io, each with the Nix daemon, s6-overlay process supervision, and home-manager user configuration.
| Variant | Purpose | User | Port |
|---|---|---|---|
| nixpod | General development | root (uid 0) | -- |
| ghanix | GitHub Actions runners | runner (uid 1001) | -- |
| codenix | code-server IDE | jovyan (uid 1000) | 8888 |
| jupnix | JupyterLab | jovyan (uid 1000) | 8888 |
Variant details
The nixpod container is the base variant with home-manager activated for root, intended for general-purpose development and debugging including scenarios like Kubernetes ephemeral containers.
The ghanix container is configured for GitHub Actions self-hosted runners with the runner user and includes the atuin daemon service.
The codenix container runs code-server on port 8888 with the jovyan user, includes VS Code extension installation and home-manager activation via s6 init scripts, and the atuin daemon.
The jupnix container runs JupyterLab on port 8888 with the jovyan user and includes the atuin daemon and home-manager activation.
All variants share a common base image built by modules/containers/build-image.nix with four ordered layers (base utilities, Nix daemon, s6-overlay, Nix configuration) plus a variant-specific customization layer.
Pull and run a prebuilt image:
docker pull ghcr.io/cameronraysmith/nixpod:latest
docker run -it --rm ghcr.io/cameronraysmith/nixpod:latestBuild from source and load into the local Docker daemon:
nix run .#load-nixpodThis uses skopeo with the nix2container transport to copy the image directly, without producing a full tarball. On macOS, the loader automatically targets the corresponding Linux architecture.
Building containers on macOS (Apple Silicon)
Building container images requires a Linux builder since nix2container produces Linux derivations.
On Apple Silicon Macs, nix-rosetta-builder provides an x86_64-linux and aarch64-linux builder via a lightweight Linux VM using Rosetta 2 translation.
To set it up, add the flake input and import the nix-darwin module:
# flake.nix
inputs.nix-rosetta-builder.url = "github:cpick/nix-rosetta-builder";Then enable it in your nix-darwin configuration:
{
imports = [ inputs.nix-rosetta-builder.darwinModules.default ];
nix-rosetta-builder = {
enable = true;
onDemand = true;
cores = 12;
memory = "48GiB";
diskSize = "500GiB";
};
}The onDemand option powers off the VM when idle to save resources.
Adjust cores, memory, and diskSize to match available system resources.
See the nix-rosetta-builder README for the full set of configuration options.
The nix-rosetta-builder VM image is an aarch64-linux NixOS system, so building it requires a Linux builder, which creates a chicken-and-egg problem on a fresh macOS system that has no Linux builder yet.
nix-darwin ships a native nix.linux-builder module that provisions a QEMU-based Linux VM.
This VM can build aarch64-linux derivations, which is sufficient to build the nix-rosetta-builder VM image.
The bootstrap uses this native builder as a temporary stepping stone.
Step 1 β enable the native linux-builder and apply the configuration:
{
nix.linux-builder.enable = true;
}darwin-rebuild switchThis starts a QEMU VM that registers itself as a nix.buildMachines entry for aarch64-linux builds.
Step 2 β with the native builder running, enable nix-rosetta-builder and disable the native one:
{
imports = [ inputs.nix-rosetta-builder.darwinModules.default ];
nix.linux-builder.enable = false;
nix-rosetta-builder = {
enable = true;
onDemand = true;
cores = 12;
memory = "48GiB";
diskSize = "500GiB";
};
}darwin-rebuild switchThe native linux-builder provides the aarch64-linux build capability needed to build the nix-rosetta-builder VM image during this second darwin-rebuild switch.
Once nix-rosetta-builder is running, it registers itself as the Linux builder and can rebuild its own VM image on future activations, so the native builder is no longer needed.
Setting nix.linux-builder.enable = false is permanent.
It prevents running two Linux VMs for the same purpose and triggers a nix-darwin cleanup activation that removes the native builder's working directory at /var/lib/linux-builder.
The native builder would only need to be re-enabled if nix-rosetta-builder were completely removed and reinstalled from scratch.
Enter the development shell:
nix developnix build # build default home-manager activation package
nix build .#nixpod # build nixpod container (nix2container JSON manifest)
nix build .#codenix # build code-server container
nix build .#ghanix # build GitHub Actions runner container
nix build .#jupnix # build JupyterLab container
nix run .#load-nixpod # load nixpod into Docker daemon via skopeo
nix fmt # format nix files via treefmt (nixfmt)
nix flake check # validate flake, run nix-unit tests and pre-commit checksJustfile recipes
The justfile provides grouped convenience commands.
Run just to see all available recipes or just -n <recipe> for a dry run.
Nix operations: just build, just check, just lint, just io, just update, just clean
Container lifecycle: just container-build, just container-load, just container-run, just container-push, just container-push-all, just container-build-all
CI helpers: just gh-ci-run, just gh-workflow-status, just gh-watch, just gh-logs, just gh-rerun, just gh-cancel
Secrets management: just show-secrets, just edit-secrets, just scan-secrets, just export-secrets, just validate-secrets, and additional sops utilities
Release management: just test-release, just preview-version, just release
Devpod operations: just pod, just devpod, just provider
Output summary
packages (per system: aarch64-darwin, aarch64-linux, x86_64-darwin, x86_64-linux)
default-- home-manager activation packageload-nixpod,load-ghanix,load-codenix,load-jupnix-- scripts that load images into Dockerpush-nixpod,push-ghanix,push-codenix,push-jupnix-- push arch-qualified images to ghcr.io
Linux-only packages (aarch64-linux, x86_64-linux):
nixpod,ghanix,codenix,jupnix-- nix2container JSON image manifestscontainer-- alias for nixpodnixpod-users-- system user identity derivation (passwd, group, shadow, PAM)s6-overlay-layer-- s6-overlay filesystem layout
apps (per system)
load-nixpod,load-ghanix,load-codenix,load-jupnix-- container loader appspush-nixpod,push-ghanix,push-codenix,push-jupnix-- per-arch image push appsnixpodManifest,ghanixManifest,codenixManifest,jupnixManifest-- multi-arch manifest assembly
devShells
default-- development shell with build and operations tooling
checks
nix-unit-- 12 evaluation-time tests for flake structure and container invariantspre-commit-- git-hooks.nix pre-commit checkstreefmt-- treefmt formatting validation
formatter -- treefmt (nixfmt)
homeModules -- default home-manager module (atuin, git, neovim, starship, terminal, zsh with catppuccin theming)
legacyPackages -- homeConfigurations (root, jovyan, runner) and containerMatrix for CI matrix discovery
Enter the development shell with nix develop or via direnv if configured.
The shell provides:
- build and CI: just, act, nix-output-monitor, ratchet
- secrets: age, sops, ssh-to-age, gitleaks
- release: bun, nodejs (for semantic-release)
- pre-commit hooks: treefmt (nixfmt) and gitleaks secret scanning via git-hooks.nix
Format all Nix files with nix fmt.
Validate the flake and run pre-commit checks with nix flake check.
The project is built on Nix Flakes with flake-parts for modular output composition.
The flake uses import-tree to auto-discover flake-parts modules from the modules/ directory, eliminating manual import lists.
Container images are constructed with nix2container's buildImage and buildLayer for deferred tar creation and efficient layer management.
s6-overlay provides process supervision within containers.
Home-manager user configurations use deferred module composition via the flake.modules.homeManager.* namespace.
Multi-arch publishing uses a decoupled push/manifest model: skopeo pushes per-arch images via the nix2container transport, then crane assembles multi-platform manifest lists.
- nix2container -- deferred tar creation for container images
- s6-overlay -- process supervision in containers
- flake-parts -- modular flake output composition
- import-tree -- auto-discovery of flake-parts modules
- home-manager -- declarative user environment configuration
- nix-unit -- evaluation-time unit testing for Nix expressions
- crane -- multi-arch manifest assembly
- nix-rosetta-builder -- Rosetta 2-accelerated Linux builder for Apple Silicon
- catppuccin -- theming for terminal and editor configuration
- nix-snapshotter -- CRI-layer container integration
- vanixiets -- reference nix-darwin and home-manager patterns