This tutorial will guide you through creating a Docker container that utilizes Nix for package management, incorporating Nix Flakes to manage dependencies and configurations. By the end, you'll have a functional Docker container configured with Nix, built using the exact code provided in the solution directory, in case you struggle with it.
First, in an this folder, create a file named Dockerfile
. This file will contain instructions for building your Docker image.
Begin by specifying the base image for your Docker build. Add the following line to your Dockerfile
:
FROM nixos/nix:latest
This line tells Docker to use the latest NixOS image as the foundation for your container. The nixos/nix
image includes the Nix package manager, providing a minimal environment with Nix pre-installed.
Define the working directory inside the container where subsequent commands will be executed. Add the following line:
WORKDIR /workdir
This command sets /workdir
as the working directory. If this directory doesn't exist, Docker will create it automatically. Setting a working directory helps organize files and ensures that commands run in the intended location.
To utilize Nix Flakes, you need to enable experimental features in Nix. Add the following line:
RUN mkdir -p /etc/nix && echo "experimental-features = nix-command flakes" > /etc/nix/nix.conf
This command creates the /etc/nix
directory (if it doesn't exist) and writes a configuration line to /etc/nix/nix.conf
to enable the nix-command
and flakes
experimental features. This step is essential for using Nix Flakes, as they are still experimental and require explicit enabling.
Transfer the contents of your current directory on the host machine into the container's /workdir
directory by adding:
COPY . /workdir/
This command copies all files from your current directory to the /workdir
directory inside the container, allowing the Docker build process to access your project's files, including the flake.nix
file.
Instruct Docker to build the default package specified in the flake.nix
file for the appropriate system architecture:
RUN nix build .#defaultPackage.${system}-linux
This command uses nix build
to compile the package defined in the flake's outputs. The .#defaultPackage.${system}-linux
syntax specifies the defaultPackage
attribute for the ${system}
system (could have any name instead of defaultPackage, multiple are possible as well ). If you're using a different architecture (e.g., x86_64-linux
for most desktop PCs or aarch64-linux
on Apple M Chips ), adjust this line accordingly.
Ensure that the binaries from the build result are accessible by updating the PATH
environment variable:
ENV PATH="/workdir/result/bin:${PATH}"
This line prepends the path to the build result's binary directory to the existing PATH
, allowing the system to locate and execute these binaries without needing their full paths.
Specify the default command to run when the container starts:
CMD ["/bin/sh"]
This command tells Docker to launch a shell (/bin/sh
) when the container runs, enabling interactive use or further command execution.
Alternatively, you could also configure to launch a in the flake preconfigured service, like gitlab on a specific port or the cowsay command form our example.
In the same directory, create a file named flake.nix
with the following content:
{
description = "Flake for nixos-container";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11";
};
outputs = { self, nixpkgs }: let
system = "x86_64-linux"; # Change to "aarch64-linux" if using an Apple M Chip
pkgs = import nixpkgs { inherit system; };
in {
defaultPackage.${system} = pkgs.buildEnv {
name = "tools";
paths = with pkgs; [
cowsay
vim
];
};
};
}
I know this is a lot thrown at you. Large parts of it are just boilerplate. below is a description what happens. But no worries if you dont get it instantly, this is just a simple showcase. After is a link to excercises on the Nix language, letting some things become more clear.
This flake.nix
file defines the Nix Flake, specifying inputs and outputs for the project:
- description: Provides a brief description of the flake's purpose.
- inputs: Specifies the dependencies of the flake. Here, it references the
nixpkgs
repository from GitHub, pinned to thenixos-24.11
branch. This ensures that the flake uses a specific version of the Nix Packages collection, promoting reproducibility. - outputs: Defines what the flake produces.
system
: Specifies the target system architecture. Change this to match your system ("x86_64-linux"
for most PCs,"aarch64-linux"
for ARM-based systems like Apple's M1).pkgs
: Imports thenixpkgs
package set for the specified system, allowing access to packages compatible with that architecture.defaultPackage.${system}
: Sets the default package output for the specified system architecture.pkgs.buildEnv
: Creates an environment that bundles multiple packages together.name
: Names the environment "tools".paths
: Lists the packages to include in the environment. Here,cowsay
andvim
are included.
With both Dockerfile
and flake.nix
in place, you can now build and run your Docker image:
# Build the Docker image
docker build -t nix-flake-container .
# Run the Docker container
docker run -it nix-flake-container
The docker build
command creates the Docker image and tags it as nix-flake-container
. The docker run
command starts a new container from the image in interactive mode (-it
), allowing you to interact with the shell inside the container.
Once inside the container's shell, verify that the specified packages (cowsay
and vim
) are installed and accessible:
# Check cowsay installation
cowsay "Hello, Nix!"
# Check vim installation
vim --version
If both commands execute successfully, it confirms that your Docker container is correctly set up with Nix and the specified packages.
Continue by searching through the Nix packages and add an addtional program, If you got no idea: you could write a simple java or c hello world using vim, search for the packages containing the dependencies needed and add them to the list.
Finally, start with these short excercises on Nix (O)