Deploying nix-shell Python Dependencies to Kubernetes

Over the last year or two of running NixOS, I no longer keep any python packages on my system:

~ > pip
The program 'pip' is not in your PATH. It is provided by several packages.
You can make it available in an ephemeral shell by typing one of the following:
  nix-shell -p python311Packages.pip
  nix-shell -p python312Packages.pip

Instead, I’ll keep a default.nix config in the top level of a project:

{ pkgs ? import <nixpkgs> { } }:

let
  pythonEnv = pkgs.python312.withPackages (ps: with ps; [
    flask
    requests
    pillow
    waitress
    pip
  ]);
in

if builtins.getEnv "BUILD" == "1"
then pythonEnv
else pkgs.mkShell {
  name = "python-dev-env";
  buildInputs = [
    pythonEnv
  ];
}

…which allows me to run something like this:

nix-shell . --run 'python3 app.py --serve'

I’ll often make a just target for such commands:

run: lint
  nix-shell . --run 'python3 app.py --serve'

This allows me to cd into my projects and run something like just run or just test.


With all this in place, the next issue is how to make container images for these projects that have the same packages that nix is giving me.

A sort of hacky way to do this I’ve come up with is as follows. See the conditional in default.nix above?

if builtins.getEnv "BUILD" == "1"
then pythonEnv

This gets nix to build a derivation that we’re able to install via nix-env, which happens in the Dockerfile:

from nixos/nix
arg BUILD=1

copy default.nix .
run nix-channel --add https://nixos.org/channels/nixos-24.05 nixpkgs &&\
    nix-channel --update &&\
    nix-env -if default.nix

workdir /opt
copy app.py .

cmd ["python3", "app.py", "--serve"]

We’re also setting the nix-channel to the same as my workstation and forcing an update - which I know is a bit of a docker anti-pattern, but there aren’t verion-pinned nixos/nix containers so this is the next best thing.


The result of all this is that the image running in our kubernetes deployment is running the same python packages as my workstation - with near zero ongoing effort:

imgproxy-lite > nix-shell . --run 'pip freeze'
blinker==1.7.0
brotlicffi==1.1.0.0
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
defusedxml==0.7.1
Flask==3.0.3
idna==3.7
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
olefile==0.47
pillow==10.3.0
pycparser==2.22
requests==2.31.0
urllib3==2.2.2
waitress==3.0.0
Werkzeug==3.0.4
~ > kubectl exec -it deployments/imgproxy-lite -- pip freeze
blinker==1.7.0
brotlicffi==1.1.0.0
certifi==2024.2.2
cffi==1.16.0
charset-normalizer==3.3.2
click==8.1.7
defusedxml==0.7.1
Flask==3.0.3
idna==3.7
itsdangerous==2.2.0
Jinja2==3.1.4
MarkupSafe==2.1.5
olefile==0.47
pillow==10.3.0
pycparser==2.22
requests==2.31.0
urllib3==2.2.2
waitress==3.0.0
Werkzeug==3.0.4

This isn’t going to work on teams where everyone isn’t running nix, or in situations where we actually want to pin packages. For my own small projects however, this makes managing dependencies for python-kuberetes projects extremely low effort. There is also a bit of a dogfooding aspect with regard to the NixOS release cycle, but again for my own small projects this is fine.

Nathan Hensel

on caving, mountaineering, networking, computing, electronics


2024-11-19