A Python Engineer's Favorite Development Environment

2025 Edition (NixOS)

PyCon 25, Bologna 2025

A Python Engineer's Favorite Development Environment

2025 Edition (NixOS)

Nix(OS) for Python Developers

Peter Bittner. Developer.

Of People, Companies and Code.

Painless Software

3. Repo Design.

Beautiful is better than ugly. System and User configuration.

2. Getting Started.

Learning resources, downloads and basic commands.

1. Why use NixOS.

Config management for Dev machines.

4. The Nix language.

Modules and Lambda functions.

5. Develop your NixOS Config.

Working with a tool you don't know yet.

6. Python on NixOS.

Make development a breeze on a non-compliant system.

Digital Agency Problems

  • Failing projects (over time, over budget)
  • Slow releases (manual, big bang)
  • Technical debt & Kubernetes
  • Cloud complexity (multi/hybrid)
  • Inefficient processes (onboarding)
  • AI will replace us all 😱

Fix-Price Projects & Agile

Continuous Delivery

Modern Continuous Delivery

NixOS

NixOS

Juju & Charmed Operators

Development Process

Continuous Delivery (CI/CD)

feature

branch

production

system

approve

review

system

open PR/MR

build

server

push code

Release early, release often!

CD Pattern Anti-Pattern
Automate environment config Manual setup, "work of art"
Scripted setup Notes, documentation of setup
Single command builds Comprehensive instructions
Fix broken environments first Ignore errors, postpone fixes
TDD, automate tests Manual verification + fixes
Fully version controlled Parts not version controlled
Visibility, dashboards, sharing Geeky "secret configurations"

Your Laptop - The Missing Piece

Easy Replacement

Portable, declarative configuration. Effortless upgrades of hardware and software.

1.

A Disposable Unit

2.

Resilience Built-In

Work on any office owned computer. Continue where you left off when device gets dropped or stolen.

➜ Make installation and backup fully automatic, restore data upon login.

3.

Fun Team Investment

Learn by improving a shared setup. Solve problems together.

➜ Treat every machine like a container with a data volume. Resist manual config changes. Get into the IaC mindset, naturally.

3. Repo Design.

Beautiful is better than ugly. System and User configuration.

2. Getting Started.

Learning resources, downloads and basic commands.

1. Why use NixOS.

Config management for Dev machines.

4. The Nix language.

Modules and Lambda functions.

5. Develop your NixOS Config.

Working with a tool you don't know yet.

6. Python on NixOS.

Make development a breeze on a non-compliant system.

Package Manager & Distro

Nix = purely functional package manager

Nix store = /nix/store (contains packages aka derivations)

Nix expressions = functional language to describe/build a package

NixOS = Linux distribution based on & built entirely from Nix

NixOS config = /etc/nixos/configuration.nix (default, example)

FHS ?!?

Functional?!? WTF!

Fear not, Pythonistas! —

You write pythonic code, you do functional.

foo = [99, 3, 4, 0]
id(foo)  # 140428019716160

bar = sorted(foo)
id(bar)  # 140428019721728

inverse = list(reversed(bar))
print(inverse)  # [99, 4, 3, 0]

# not pure functional
result = foo.sort()
id(foo)  # 140428019716160
print(foo)  # [0, 3, 4, 99]
print(result)  # None
words = ["Ciao", "Python", "Italia"]
upper_words = list(map(str.upper, words))
upper_list = [str.upper(s) for s in words]

print(upper_words)  # ['CIAO', PYTHON', 'ITALIA']
print(upper_list)  # ['CIAO', PYTHON', 'ITALIA']

salutation = "Ciao" if friend else "Buongiorno"

(lambda :-1)()  # -1
sorted(words, key=lambda s: -len(s))  # ..., Ciao

list comprehensions

set comprehensions

dictionary comprehensions

generator comprehensions

lambda expressions

built-in functions

Commands & Places

nix-channel nix flake [init|check|show|update|...]
nix-shell -p <pkg> ... nix shell <repo>#<pkg> ...
nix-shell -p <pkg> --run "<cmd> ..." nix run <repo>#<pkg> ...
nix-shell [default.nix|shell.nix] nix develop
nix-instantiate --eval [--expr] ... nix eval [<repo>#<pkg>|--expr ...|--file ...]
search.nixos.org nix search nixpkgs <regex>

New (think "Python 3")

Old (think "Python 2")

Excutables /run/current-system/sw/bin/
User executables /etc/profiles/per-user/<username>/bin/
Application data /run/current-system/sw/share/
User app data /etc/profiles/per-user/<username>/share/
Application icons /run/current-system/sw/share/icons/hicolor/scalable/apps/org.gnome.Epiphany.svg
User app icons /etc/profiles/per-user/<username>/share/icons/
Autostart items /run/current-system/sw/etc/xdg/autostart

Interesting Places

configuration.nix

$ tree /etc/nixos
/etc/nixos
|-- configuration.nix
`-- hardware-configuration.nix

# Edit this configuration file to define what s
{ config, pkgs, ... }:

{
  imports = [
    ./hardware-configuration.nix
  ];

  boot.loader.systemd-boot.enable = true;

  networking.hostName = "nixos";
  networking.networkmanager.enable = true;

  time.timeZone = "Europe/Rome";
  i18n.defaultLocale = "en_US.UTF-8";

  programs.firefox.enable = true;

  environment.systemPackages = with pkgs; [
    vim
  ];
}
# Do not modify this file!  It was generated by
{ config, lib, pkgs, modulesPath, ... }:

{
  boot.kernelModules = [ "kvm-intel" ];
  fileSystems."/" = {
    device = "/dev/disk/by-uuid/5a42e090-37b7";
    fsType = "ext4";
  };
  networking.useDHCP = lib.mkDefault true;
}

Apply Your Changes

$ sudo nixos-rebuild [switch|boot|test]
...
Done. The new configuration is /nix/store/j6a42...

$ nixos-rebuild --use-remote-sudo [switch|boot|test]
...
Done. The new configuration is /nix/store/j6a42...

a

b

Recommended

Use Version Control

$ sudo su -

# cd /etc/nixos

# git init && git add -v .

# git commit -m "Profit!"

$ cp -r /etc/nixos ~/.nixos-config

$ cd ~/.nixos-config

$ git init && git add -v .

$ git commit -m "Profit!"

$ git push origin main

$ sudo rm /etc/nixos/*

$ nixos-rebuild --use-remote-sudo --file configuration.nix

Recommended

🫣

🙂

1.a

1.b

2

🤑

3. Repo Design.

Beautiful is better than ugly. System and User configuration.

2. Getting Started.

Learning resources, downloads and basic commands.

1. Why use NixOS.

Config management for Dev machines.

4. The Nix language.

Modules and Lambda functions.

5. Develop your NixOS Config.

Working with a tool you don't know yet.

6. Python on NixOS.

Make development a breeze on a non-compliant system.

Freedom And Chaos

  • Your configuration grows ➜ break it apart, use imports
  • NixOS doesn't prescribe how to structure your repository
  • Search for examples online (e.g. "nixos-config")

Host-based Configuration

  • hosts/ – managed machines
  • roles/ – classes of machines (abstraction layer)
  • system/ – system-global configuration
  • home/ user configuration
  • user/ – common user configuration

Home Manager

Manage user-specific software and settings ("dotfiles")

💡 Some people use GNU Stow instead to manage dotfiles

Disko

Declarative disk partitioning

disko-config.nix

$ sudo nix run github:nix-community/disko/latest -- \
   --mode destroy,format,mount \
   --flake gitlab:painless-software/nixos-config#example

3. Repo Design.

Beautiful is better than ugly. System and User configuration.

2. Getting Started.

Learning resources, downloads and basic commands.

1. Why use NixOS.

Config management for Dev machines.

4. The Nix language.

Modules and Lambda functions.

5. Develop your NixOS Config.

Working with a tool you don't know yet.

6. Python on NixOS.

Make development a breeze on a non-compliant system.

The Nix Language

{
  # numbers
  a = 1; f = 3.14;

  # strings
  text = "Hello Pi! ${f}";
  multi_line = ''
    Very long text ... with ${a}
  '';

  # boolean
  truth = false; lie = true;

  # attribute sets
  b = { foo = 1; bar = "something"; };

  # lists
  c = [ 1 2 3 ];
  d = [ "hello" "world" ];
  
  # paths
  x = /absolute/path/to
  y = ./relative/path
  z = ~/.config # path in home dir
}

data types

{
  # negation
  truth = !false;

  # string and paths
  a_str = "Dirs" + ~/.config/user-dirs.dirs;
  a_path = /etc + "/nixos";

  # list concatenation
  l = [ 1 2 3 ] ++ [ "four" "five"];
  
  # logical and, logical or
  m = true && false; n = true || false;
  
  # update attribute set
  s = { x = 1; } // { y = 1; x = 2; };
  
  # has attribute (attribute set member)
  h = { x = 42; } ? x;
}

operators

d = builtins.readDir;
f = builtins.readFile;
e = builtins.getEnv "HOME"; ...

built-ins

Nix File, Literals & Lambda

42

a.nix

"foo"

b.nix

true

c.nix

~/.local/bin

d.nix

[ ]

e.nix

{ }

f.nix

x: x + 1

g.nix

{
  x = import ./a.nix;
}

imported.nix

{ lib, ... }:

{
  imports = [ ];

  options = {
    my.option = lib.mkOption { };
  };

  config = { };
}

explicit.nix

Module

{ lib, ... }:

{
  options = {
    desktop.style = lib.mkOption {
      type = lib.types.enum [ "Windows" "macOS" "GNOME" ];
      default = "GNOME";
    };
  };
}

options-only.nix

{
  console.keyMap = "it";

  services.xserver.xkb = {
    layout = "it";
    variant = "";
  };
}

implicit-config.nix

Nix Module

{ lib, ... }:

{
  imports = [
    ./file.nix
  ];

  options = {
    magicNumber = lib.mkOption {
      type = lib.types.number;
      default = 3.14;
    };
  };

  config = {
    boot.loader.grub.enable = true;
  };
}

module.nix

{ lib, ... }:

let
  foo = "Hello";
in
{
  imports = [
    ./file.nix
  ];

  options = {
    greeting = lib.mkOption {
      type = lib.types.str;
      default = foo;
    };
  };

  config = {
    boot.loader.grub.enable = true;
  };
}

variables.nix

Nix Flake

{
  description = "Python Italia flake";

  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
  	nixos-hardware.url = "github:NixOS/nixos-hardware?ref=master";
  };

  outputs = { self, nixpkgs }: {
    nixosConfigurations = nixpkgs.lib.mapAttrs mkSystem systems;
  };
}

flake.nix

  • Keep flake content minimal
  • Let flake be only the entry point

$ nix flake update

Recommendation

Nix Python
Flake, flake.nix, flake.lock pyproject.toml, uv.lock
Module, <filename>.nix Module, <filename>.py
Lambda expression, function Python code (module)

TL;DR - Concepts

3. Repo Design.

Beautiful is better than ugly. System and User configuration.

2. Getting Started.

Learning resources, downloads and basic commands.

1. Why use NixOS.

Config management for Dev machines.

4. The Nix language.

Modules and Lambda functions.

5. Develop your NixOS Config.

Working with a tool you don't know yet.

6. Python on NixOS.

Make development a breeze on a non-compliant system.

Use Technology's QA Tools

$ copier copy gl:painless-software/cicd/config/nixos nixos-config
...

$ pre-commit install

...

$ git init

$ git add -v .

...

$ pre-commit

$ nix flake check

...

---
deadnix:
  extends: .nix
  script: nix-shell -p deadnix --run "deadnix --fail"

statix:
  extends: .nix
  script: nix-shell -p statix --run "statix check"

flake:
  extends: .nix
  script:
  - nix flake check
  - nix flake show

disko:
  extends: .nix
  script: nix run github:nix-community/disko/latest --
    --mode destroy,format,mount --dry-run --flake .#generic

tooling:
  extends: .megalinter
  variables:
    FLAVOR: documentation

.gitlab-ci.yml

Copier template

Iterative Development

1.

Flake

Establish execution entrypoint (e.g. integrate hosts/).

Consider integrating QA tools, pre-commit and CI/CD early.

2.

Disko

Integrate Disko configuration. Verify installation process end-to-end.

Consider setting up functional tests with VMs.

4.

Refine

Delegate host setup to system/ configuration. Consider introducing roles/.

Consolitate home/  features in common user/ modules.

3.

Home Manager

Allow configuring settings and installing software for individual users.

Consider using LDAP  for a flexible, host-independent setup.

3. Repo Design.

Beautiful is better than ugly. System and User configuration.

2. Getting Started.

Learning resources, downloads and basic commands.

1. Why use NixOS.

Config management for Dev machines.

4. The Nix language.

Modules and Lambda functions.

5. Develop your NixOS Config.

Working with a tool you don't know yet.

6. Python on NixOS.

Make development a breeze on a non-compliant system.

Python on NixOS

# system/software/developer.nix
{ pkgs, ... }:

{
  environment.systemPackages = with pkgs; [
    python313 # first in list will be default Python
    python312
    python311
    python310
    uv
  ];
}
# system/software/developer.nix
{ pkgs, ... }:

{
  environment.systemPackages = with pkgs; [
    uv
  ];
}

a

b

system-managed

user-managed

$ uv python install --preview --default

$ which python

/home/nixos/.local/bin/python

Cannot open shared object file

You cannot mix system Pythons with Python packages that ship precompiled binary code.

$ uv venv

Using CPython 3.13.3 interpreter at: /run/current-system/sw/bin/python
Creating virtual environment at: .venv

$ source .venv/bin/activate

$ uv pip install numpy

 + numpy==2.2.6

$ python

>>> import numpy
Traceback (most recent call last):

  ...

ImportError: libstdc++.so.6: cannot open shared object file: No such file or directory

Uv Tool Interface

$ uv tool install copier

$ uv tool install httpie

$ uv tool install pre-commit

$ uv tool install pyclean

$ uv tool install tox

# system/terminal/default.nix
{
  environment.localBinInPath = true;
}

$ ls ~/.local/bin
copier  http  httpie  https  pre-commit  pyclean  python  tox

$ uv tool list

$ uv tool upgrade --all

What did he
forget to explain?

I think it was

nix develop

And how to configure backup and restore

And automatic system upgrades

Thank you!

for your precious time

Painless Software

Less pain, more fun.

Most backgrounds from Unsplash (CC BY-SA)
Comic from Work Chronicles (CC BY-SA)
Decorative icons are unicode (CC-0)

PyCon 25: Nix(OS) for Python Developers

By Peter Bittner

PyCon 25: Nix(OS) for Python Developers

How do you onboard new colleagues in 2025? How long does it take? Would you love a standardized setup under version control that everyone can customize for themselves? A stable desktop setup, reinstalled in just minutes. It can be done. https://2025.pycon.it/it/event/a-python-engineers-favorite-development-environment-2025-edition-nixos

  • 197