Installing Nix in Single-User Mode in a Container (Without sudo)

A practical guide to setting up Nix in single-user mode inside a Podman rootless container using buildpack-deps:24.04 image with persistent storage. Covers the issues you’ll hit along the way and how to fix each one. Same setup works with Docker.

Goal

Run Nix in single-user mode inside a container where:

  • /nix and /home are mounted as persistent volumes

  • No sudo or additional system packages are installed

  • Packages are managed entirely through Nix

  • The setup survives container rebuilds

Why single-user mode

Single-user Nix doesn’t require a running daemon or root access. The entire Nix store lives under /nix, and the profile lives under the user’s home directory. This makes it a natural fit for containers where you want minimal system-level changes and have pre-mounted persistent volumes.


Step 0: Prepare host directories

The buildpack-deps:24.04 image already includes an ubuntu user (uid 1000) with a home directory. We’ll copy it out to a host directory so it persists across container rebuilds.

mkdir -p buildbox/nix
cd buildbox

Start a temporary container and copy the home directory out:

podman run -d --name tmp buildpack-deps:24.04 sleep 60
podman cp tmp:/home/ubuntu .
podman rm -f tmp

This gives you:

buildbox/
├── nix/        # will hold the Nix store
└── ubuntu/     # the ubuntu user's home directory from the image

Important: Check that the permissions on both directories match uid 1000, since the container runs as the ubuntu user:

ls -lan nix ubuntu

If needed, fix ownership:

sudo chown -R 1000:1000 nix ubuntu

Step 1: Start the container

podman run -it --name buildbox \
  --userns keep-id \
  -v ./ubuntu:/home/ubuntu \
  -v ./nix:/nix \
  --user ubuntu \
  --workdir /home/ubuntu \
  buildpack-deps:24.04

The --userns keep-id flag maps your host user to uid 1000 inside the container, which matches the ubuntu user. This ensures file permissions work correctly across the mounted volumes without running as root.

With Docker, the equivalent would be:

docker run -it --name buildbox \
  -v ./ubuntu:/home/ubuntu \
  -v ./nix:/nix \
  --user ubuntu \
  --workdir /home/ubuntu \
  buildpack-deps:24.04

Step 2: Install Nix

curl --proto '=https' --tlsv1.2 -L https://nixos.org/nix/install | sh -s -- --no-daemon

The --no-daemon flag selects single-user installation. The installer will complete without errors.

Gotcha: Nix won’t be on your PATH after this.

The installer adds a sourcing line to ~/.profile:

if [ -e /home/ubuntu/.nix-profile/etc/profile.d/nix.sh ]; then
  . /home/ubuntu/.nix-profile/etc/profile.d/nix.sh
fi

But your container shell is /bin/bash launched as a non-login interactive shell, which reads ~/.bashrc, not ~/.profile. So Nix is installed but invisible.

Fix: Add the sourcing line to ~/.bashrc:

echo 'if [ -e /home/ubuntu/.nix-profile/etc/profile.d/nix.sh ]; then . /home/ubuntu/.nix-profile/etc/profile.d/nix.sh; fi' >> ~/.bashrc

Step 3: Source the Nix environment

Before opening a new shell, you can source it manually:

. /home/ubuntu/.nix-profile/etc/profile.d/nix.sh

Gotcha: This may silently do nothing.

The nix.sh script guards its entire body with:

if [ -n "${HOME-}" ] && [ -n "${USER-}" ]; then
    # ... all the PATH setup ...
fi

In containers, USER is often not exported as an environment variable. When this condition fails, the script executes without error and without doing anything. No warning, no output! The PATH is simply unchanged.

Fix: Export USER before sourcing:

export USER=ubuntu
. /home/ubuntu/.nix-profile/etc/profile.d/nix.sh

To make this permanent, update ~/.bashrc with both lines:

cat > ~/.bashrc << 'EOF'
export USER=ubuntu
if [ -e /home/ubuntu/.nix-profile/etc/profile.d/nix.sh ]; then . /home/ubuntu/.nix-profile/etc/profile.d/nix.sh; fi
EOF

Verify:

which nix
# Should print: /home/ubuntu/.nix-profile/bin/nix

Step 4: Enable required experimental features

Modern Nix usage (the nix CLI and flake references) requires two experimental features that aren’t enabled by default.

mkdir -p ~/.config/nix
cat > ~/.config/nix/nix.conf << 'EOF'
experimental-features = nix-command flakes
EOF

Without this, commands like nix profile add nixpkgs#devbox will fail with errors about disabled experimental features. The errors are shown one feature at a time, so you’d have to fix nix-command, hit the flakes error, then fix that too. Setting them in the config file avoids this entirely.

Gotcha: This config file must exist before you run any nix command.

If you run nix before creating the config, it won’t pick up the file retroactively in the same shell session — Nix reads config at startup. Either create the file first, or start a new shell after creating it.


Step 5: Install a package

nix profile install nixpkgs#devbox

This should now work without errors or warnings.

which devbox
devbox version

Complete .bashrc for reference

# Nix setup
export USER=ubuntu
if [ -e /home/ubuntu/.nix-profile/etc/profile.d/nix.sh ]; then
  . /home/ubuntu/.nix-profile/etc/profile.d/nix.sh
fi

Complete nix.conf for reference

# ~/.config/nix/nix.conf
experimental-features = nix-command flakes

Data persistence

With the volume mounts described above:

  • ./nix/ — the Nix store. All installed packages, derivations, and the database. Survives container rebuilds.

  • ./ubuntu/ — user profile, ~/.config/nix/nix.conf, ~/.bashrc, and ~/.nix-profile. Survives container rebuilds.

If you rebuild the container image, both directories live on the host. A new container starts with Nix fully set up and all previously installed packages available.


Summary of gotchas

Issue Symptom Fix
Sourcing line in .profile not .bashrc nix command not found after install Add sourcing line to ~/.bashrc
USER not set in container nix.sh runs but PATH is unchanged export USER=ubuntu before sourcing
nix-command feature disabled error: experimental Nix feature 'nix-command' is disabled Add to ~/.config/nix/nix.conf
flakes feature disabled error: experimental Nix feature 'flakes' is disabled Add to ~/.config/nix/nix.conf
1 Like

Interesting.

Is this the most minimal possible build we can muster? I’m interested in micro kernels and other techniques for attempting to make the attack surface vanishingly small like Talos Linux does.

How does this compare to the official docker image (Using Nix within Docker - Nix 2.34.8 Reference Manual) which can already be used rootless in podman and is already very minimal?

The base image could be way smaller and you can choose what ever suits your need, it’s just that buildpack-deps is solid/convenient starting point. I haven’t tested this with micro kernels yet, that would be another setup project.

1 Like

This approach is geared toward data persistence and it can be adapted to different base images(even swapping the base image while keeping your nix profile setup). Not sure how you can achieve that with the official nix docker image :man_shrugging: