Pre-baked devcontainer image for GitHub Codespaces and local devcontainers. Ships Nix, direnv, and pre-cached development shells on an Ubuntu 24.04 base.
Setting up Nix in a fresh container (installing the binary, evaluating a project flake, downloading packages) can take up to 3 minutes and is error-prone. Installing Nix on top of a stock image via features or scripts doesn't help: it repeats the same setup on every container start.
nix-aerie ships language toolchains pre-built inside the image, so nix develop works instantly instead of downloading packages on every start:
- One-click onboarding — set the image in your
devcontainer.jsonand every team member gets the same Nix + direnv environment with all dev dependencies. No install steps, no entrypoint scripts. - Beginner-friendly — new developers get the benefits of Nix shells without having to set up Nix themselves. Open the Codespace, start coding.
- Instant locally — if the image is already pulled, local devcontainers start in seconds with no download at all. On Codespaces, you can enable prebuilds for the same effect.
If you already develop locally with Nix flakes, this image isn't meant to replace that workflow. It's for Codespaces and shared devcontainers where cold start time and consistent onboarding matter.
Simply add a .devcontainer/devcontainer.json to your project. Pick the variant that matches your stack (:base, :python, or the full image with all shells) and point remoteUser to dev. If your project uses direnv, onCreateCommand takes care of trusting the .envrc on first start.
Here's a real-world example for a Python project:
.devcontainer/devcontainer.json:
To get the most out of the pre-cached shells, have your project's flake.nix follow the same nixpkgs pin as nix-aerie. This way nix develop finds everything in the Nix store and resolves instantly:
flake.nix:
{
inputs = {
nix-aerie.url = "github:eth-library/nix-aerie";
nixpkgs.follows = "nix-aerie/nixpkgs";
};
outputs = { self, nixpkgs, ... }:
let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
in
{
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
packages = [
pkgs.python312
pkgs.uv
];
};
});
};
}The key line is nixpkgs.follows = "nix-aerie/nixpkgs". This ensures your project uses the exact same nixpkgs revision that the image was built with, so all store paths match. Without it, nix develop still works but downloads packages from cache.nixos.org instead of using the pre-cached ones.
The image builds on Ubuntu 24.04 for the conventional FHS layout, glibc, and apt-get, the things devcontainer features and standard Linux tooling expect. A familiar starting point for developers new to Nix: no need to learn NixOS to benefit from reproducible toolchains.
All developer tools (bash, git, direnv, curl, jq, and language shells) are managed by Nix through a single flake.nix with one flake.lock. Reproducible, pinned, and pre-cached in the image.
The image also ships with direnv and nix-direnv pre-configured. When your project has an .envrc with use flake, opening a terminal automatically activates the Nix shell. No manual nix develop needed.
Each Nix shell closure (Python, Go, Java, K8s) is mapped to its own content-addressed OCI layer via nix2container. The shared base packages are built as a separate layer and excluded from shell layers to avoid duplication. Variants compose only the layers they need:
block-beta
columns 1
variant["Variant layers\nPython · Go · Java · K8s · …"]
shared["Shared base\nNix · bash · git · direnv · curl · jq"]
ubuntu["Ubuntu 24.04\nFHS · glibc · apt-get"]
style variant fill:#d4dce8,color:#2d3748,stroke:#7b8fa3
style shared fill:#b8c6d6,color:#2d3748,stroke:#5d7089
style ubuntu fill:#9cb0c4,color:#2d3748,stroke:#4a6478
| Variant | Image | Packages | Shell definition |
|---|---|---|---|
| Base | nix-aerie-base |
Nix, bash, git, direnv, curl, jq | flake.nix (userPackages) |
| Python | nix-aerie-python |
Python 3.12, uv | shells/python.nix |
| Go | nix-aerie-go |
Go, gopls, golangci-lint | shells/go.nix |
| Java | nix-aerie-java |
JDK 25 (headless), Maven | shells/java.nix |
| K8s | nix-aerie-k8s |
kubectl, Helm | shells/k8s.nix |
| Full | nix-aerie |
All of the above | all shells combined |
Because layers are content-addressed, pulling one variant after another only downloads the delta. Security updates push only the store paths that actually changed, not the full image.
The nixpkgs source tree (~32 MB compressed) is also included in the Nix store, so flake evaluation doesn't need to download nixpkgs first. This saves time on every nix develop call regardless of whether your pins match the image.
- nix2container builds the image without Docker. Content-addressed layers mean updates push only the store paths that changed, not the full image.
- Single-user Nix, the container standard; no daemon or systemd required.
USER=devin the image, ready for devcontainers anddocker runout of the box.- One
flake.lockpins all shells to the same nixpkgs revision, guaranteeing store-path deduplication across variants. - Architecture: Multi-arch (
linux/amd64+linux/arm64) published as OCI manifests —docker pullselects the native image automatically, including on Apple Silicon.
{ "name": "Pre-baked devcontainer including Nix flakes, Python, uv, and direnv", "image": "ghcr.io/eth-library/nix-aerie-python:latest", "remoteUser": "dev", "onCreateCommand": "direnv allow .", "customizations": { "vscode": { "extensions": [ "jnoortheen.nix-ide", "ms-python.python" ], "settings": { "python.defaultInterpreterPath": "${workspaceFolder}/.venv/bin/python", "python.terminal.activateEnvironment": false } } } }