This project serves as a foundational template for integrating Nix, Python, Dev Containers, and GitHub Actions into your development workflow. It provides a robust and reproducible environment, ideal for both new users exploring declarative development and experienced practitioners seeking streamlined setups.
To begin, ensure your system has the following prerequisites installed:
- Docker: Install Docker
- Visual Studio Code (VS Code): Download VS Code
- Dev Containers Extension: Install from the VS Code Marketplace
Clone the repository from GitHub:
git clone https://github.com/custom-starter-kits/vscode-remote-try-nix--with-python.git
Make sure Docker is running on your machine. You can verify this by opening a terminal and running:
docker --version
Note
If Docker is not running, start the Docker or Docker Desktop application on your computer.
Then open the cloned repository in Visual Studio Code.
code vscode-remote-try-nix--with-python
When prompted, click Reopen in Container. VS Code will build the container and drop you into a fully configured containerized development environment.
The project includes a Makefile
with the following commands:
Creates a Python virtual environment in the specified directory (default: .venv
).
$ make venv
Installs dependencies listed in requirements.txt
into the virtual environment. Automatically creates the virtual environment if it doesn't exist.
$ make install
Starts the Flask development server on port 9000
. Automatically installs dependencies before launching.
$ make run
Removes the virtual environment directory.
$ make clean
Run the included script to see Nix in action:
./.nix/ci/workflows/nix.sh
You should see a the following output:
Python 3.13.6
To add a new package to your environment:
-
Browse https://search.nixos.org/packages?channel=unstable to find the package you want to add.
-
Open
flake.nix
-
Add the package to one or both of these lists:
installNixPackages
: for packages used in scripts and the terminalinstallNixProfilePackages
: for packages installed globally vianix profile install
Example:
installNixPackages = pkgs: [
pkgs.nodejs
];
installNixProfilePackages = pkgs: [
pkgs.nixd
pkgs.nixfmt-rfc-style
];
The Dev Container will automatically install these when it starts. To restart a Dev Container manually type F1
and type > Dev Containers: Rebuild Without Cache and Reopen in Container
.
Want to test a package without editing flake.nix
?
nix shell nixpkgs#nodejs nixpkgs#php
This opens a temporary shell with nodejs
and php
installed. You can run:
node --version
> v22.18.0
php --version
> PHP 8.4.12 (cli) (built: Aug 26 2025 13:36:28) (NTS)
> Copyright (c) The PHP Group
> Zend Engine v4.4.12, Copyright (c) Zend Technologies
> with Zend OPcache v8.4.12, Copyright (c), by Zend Technologies
Once you're done, type exit
. The packages will no longer be available outside that shell:
node --version
> bash: node: command not found
php --version
> bash: php: command not found
To update the packages defined in flake.nix
, run:
nix flake update
Nix is a powerful package manager for Linux and other Unix systems that makes package management reliable and reproducible. It is a Core Component of the Nix Ecosystem.
Nix uses a unique approach to package management by isolating dependencies and using cryptographic hashes to ensure that builds are reproducible.
Why does Nix use hash-based versioning instead of semver-based versioning?
Nix employs hash-based versioning to guarantee reproducibility, security, and integrity across development environments. Unlike semantic versioning (e.g., 1.2.3
), Nix pins packages to cryptographic hashes, not mutable identifiers.
A Hash acts as a unique digital fingerprint for any piece of data. Even the tiniest change results in an entirely different hash.
Examples:
SHA256 Hash of a file with Hello world
:
echo "Hello world" | sha256sum
> 1894a19c85ba153acbf743ac4e43fc004c891604b26f8c69e1e83ea2afc7c48f -
SHA256 Hash of a file with Hello World
:
echo "Hello World" | sha256sum
> d2a84f4b8b650937ec8f73cd8be2c74add5a911ba64df27458ed8229da804a26 -
See the difference? One tiny letter change, and the hash is totally different.
Hashes are fundamental to content identification and protection.
Example:
- Consider the gum project on GitHub.
- This package is currently fetched by Nix from GitHub using the Git tag
v0.16.0
- see here. - The Git tag
v0.16.0
currently resolves to the Git commit0d116b80685eff038aa0d436c87e4f08760ad357
- see here. - However, Git tags can be deleted and reassigned; an attacker could redirect
v0.16.0
to a potentially malicious commit. - Therefore, relying solely on tags risks installing compromised software.
Nix mitigates this by validating downloaded content against its expected hash - see here. If, in the future, the hashes do not match, Nix refuses to install the package, preventing supply chain attacks.
All Nix Flake inputs (here github:NixOS/nixpkgs/nixos-unstable
) are pinned to a specific Git commit in a lockfile called flake.lock
. This file stores this information as JSON.
The flake.lock
file ensures that Nix flakes have purely deterministic outputs. A flake.nix
file without an accompanying flake.lock
should be considered incomplete and a kind of proto-flake. Any Nix CLI command that is run against the flake β like nix build
, nix develop
, or even nix flake show
β generates a flake.lock
for you.
Here is the current flake.lock
file that pins Nixpkgs to a specific Git commit:
{
"nodes": {
"nixpkgs": {
"locked": {
"lastModified": 1756542300,
// A SHA256 hash of the contents of the flake
"narHash": "sha256-tlOn88coG5fzdyqz6R93SQL5Gpq+m/DsWpekNFhqPQk=",
// The GitHub organization
"owner": "NixOS",
// The GitHub repository
"repo": "nixpkgs",
// The specific Git commit
"rev": "d7600c775f877cd87b4f5a831c28aa94137377aa",
// The type of input
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"nixpkgs": "nixpkgs"
}
}
},
"root": "root",
"version": 7
}
If this flake.lock
were alongside a flake.nix
with this input blockβ¦
{
inputs = {
nixpkgs.url = "nixpkgs";
};
outputs = { self, nixpkgs }: {
# Define outputs here
};
}
... the nixpkgs
attribute would be implicitly pinned to the latest de60d387a0e5737375ee61848872b1c8353f945e
Git commit of the NixOS/nixpkgs/nixos-unstable
Git branch β even though that information isn't defined in the Nix code itself.
Therefore, by using a flake.lock
file, a Chain of Trust is created between the flake.nix
file and all its dependencies. This ensures that builds are both reproducible and secure, now and over the long term.
Below you will find a list of documentation for tools used in this project.
- Nix: Nix Package Manager - Docs
- Nix Flakes: An Experimental Feature for Managing Dependencies of Nix Projects - Docs
- GitHub Actions: Automation and Execution of Software Development Workflows - Docs
Thank you for your message! Please fill out a bug report.
This project is licensed under the GNU General Public License.