During the workshop, we'll try to create a development environment for a real-world Rust project with devenv.
Here's what you'll need for this workshop:
- git
- nix
- devenv
- direnv (optional)
If you haven't yet installed Nix or devenv, follow the instructions for your platform on https://devenv.sh/getting-started/.
To avoid getting rate-limited by GitHub, we recommend providing Nix with a GitHub API token.
Create a new token with no extra permissions at https://github.com/settings/personal-access-tokens/new
Add the token to your ~/.config/nix/nix.conf:
access-tokens = github.com=<GITHUB_TOKEN>
git clone https://github.com/cachix/nixcon-2024-workshop && cd nixcon-2024-workshopProjects out in the wild will often list their dependencies, setup steps, and other useful information in the README.md.
This repo has a PROJECT_README.md that lists these requirements.
We're going to use the ad-hoc instructions to build up a developer environment powered by Nix.
Lets create the initial devenv files:
devenv initThis will create the following two files:
devenv.nix: used to specify your developer environment in Nix.devenv.yaml: lets you specify dependencies on other source repositories, flakes, etc, much in the style in flake inputs.
If you have direnv installed, the shell will automatically start loading after this command.
You can also manually load the environment with:
devenv shellRunning tasks devenv:enterShell
Succeeded devenv:enterShell 10ms
1 Succeeded 10.47ms
hello from devenv
git version 2.44.0Lets remove the default configuration and start from scratch.
{ pkgs, lib, config, inputs, ... }:
{
- # https://devenv.sh/basics/
- env.GREET = "devenv";
-
- # https://devenv.sh/packages/
- packages = [ pkgs.git ];
-
- # https://devenv.sh/languages/
- # languages.rust.enable = true;
-
- # https://devenv.sh/processes/
- # processes.cargo-watch.exec = "cargo-watch";
-
- # https://devenv.sh/services/
- # services.postgres.enable = true;
-
- # https://devenv.sh/scripts/
- scripts.hello.exec = ''
- echo hello from $GREET
- '';
-
- enterShell = ''
- hello
- git --version
- '';
-
- # https://devenv.sh/tasks/
- # tasks = {
- # "myproj:setup".exec = "mytool build";
- # "devenv:enterShell".after = [ "myproj:setup" ];
- # };
-
- # https://devenv.sh/tests/
- enterTest = ''
- echo "Running tests"
- git --version | grep --color=auto "${pkgs.git.version}"
- '';
-
- # https://devenv.sh/git-hooks/
- # git-hooks.hooks.shellcheck.enable = true;
-
- # See full reference at https://devenv.sh/reference/options/
}You may have noticed a notification that devenv has detected a .env file in the project.
This file contains environment variables that are used to configure the project.
We can load them into our environment with the dotenv integration:
{ pkgs, lib, config, inputs, ... }:
{
+ dotenv.enable = true;
}$ echo $DATABASE_URL
postgres://localhost:5431/flakestry
Note
We know from the README that we'll need rust for the backend, and javascript/typescript and elm for the frontend.
Lets enable these languages in the devenv.nix file.
{ pkgs, lib, config, inputs, ... }:
{
+ languages.rust.enable = true;
+
+ languages.javascript = {
+ enable = true;
+ npm.install.enable = true;
+ };
+
+ languages.typescript.enable = true;
+
+ languages.elm.enable = true;
}Tip
Some languages support more extensive versioning support than what is available in nixpkgs.
For example, the Rust integration supports using a specific channel or an entirely custom toolchain.
- languages.rust.enable = true;
+ languages.rust = {
+ enable = true;
+ channel = "stable";
+ };This feature uses oxalica/rust-overlay under the hood.
devenv will prompt you do add it as an input to your devenv.yaml.
You can do so through the command-line:
devenv inputs add rust-overlay github:oxalica/rust-overlay --follows nixpkgsNote
This project relies on 3 main services:
- PostgreSQL as the main database.
- OpenSearch for indexing and searching for releases.
- Caddy as a reverse proxy for the frontend and backend.
Lets enable these services in the devenv.nix file.
languages.typescript.enable = true;
languages.elm.enable = true;
+
+ services.caddy.enable = true;
+ services.caddy.config = builtins.readFile ./Caddyfile;
+
+ services.opensearch.enable = true;
+
+ services.postgres = {
+ enable = true;
+ listen_addresses = "localhost";
+ port = 5432;
+ initialDatabases = [ { name = "flakestry"; } ];
+ };Launch the services with:
devenv updevenv will configure and launch the processes in an interactive process manager.
By default, this is process-compose, but we support several other implementations via process.manager.implementation.
To bring down the processes, use Ctrl+C + ENTER or run devenv processes down in another terminal (in the same directory).
You can also define custom bash scripts in devenv.nix.
+ scripts.run-migrations.exec = "sqlx migrate run";Scripts are available in the devenv shell by name.
With postgres running, we can now run the migrations:
run-migrationsNote
Now that we've set up our services, we can start adding custom processes for our backend and frontend. We'll also add a few extra packages to our shell.
services.postgres = {
enable = true;
listen_addresses = "localhost";
port = 5432;
initialDatabases = [ { name = "flakestry"; } ];
};
+
+ packages = [
+ pkgs.openssl
+ pkgs.sqlx-cli
+ pkgs.cargo-watch
+ pkgs.elm-land
+ ];
+
+ processes.backend.exec = "cd backend && cargo watch -x run";
+ processes.frontend.exec = "cd frontend && elm-land server"The backend process might fail to initialize properly if the opensearch cluster is not ready at the time of launch.
We can leverage the depends_on feature of process-compose to record this runtime ordering.
This will ensure that the backend process only starts after the opensearch and postgres services are healthy.
- processes.backend.exec = "cd backend && cargo watch -x run";
+ processes.backend = {
+ exec = "cd backend && cargo watch -x run";
+ process-compose.depends_on = {
+ opensearch.condition = "process_healthy";
+ postgres.condition = "process_healthy";
+ };
+ };
+You should now have a fully working development environment to run Flakestry!
Go to http://localhost:8888 to see it working.
There's an app in the repo to test out publishing to Flakestry. Lets add it as a script.
+ scripts.flakestry-publish.exec = "cd backend && cargo run --bin publish -- $@";Create a GitHub token with no extra permissions: https://github.com/settings/personal-access-tokens/new
export GITHUB_TOKEN=your-tokenor if you have gh installed:
export GITHUB_TOKEN=$(gh auth token)Let's try it out:
flakestry-publish --owner nixos --repo nixpkgs --version 25.11
{ pkgs, lib, config, inputs, ... }:
{
dotenv.enable = true;
packages =
[
pkgs.openssl
pkgs.cargo-watch
pkgs.elm-land
pkgs.sqlx-cli
];
languages.rust = {
enable = true;
channel = "stable";
};
languages.javascript = {
enable = true;
npm.install.enable = true;
};
languages.typescript.enable = true;
languages.elm.enable = true;
services.caddy.enable = true;
services.caddy.config = builtins.readFile ./Caddyfile;
services.opensearch.enable = true;
services.postgres = {
enable = true;
listen_addresses = "localhost";
port = 5431;
initialDatabases = [ { name = "flakestry"; } ];
};
scripts.run-migrations.exec = "sqlx migrate run";
scripts.flakestry-publish.exec = "cd backend && cargo run --bin publish -- $@";
processes = {
backend = {
exec = "cd backend && cargo watch -x run";
process-compose.depends_on = {
opensearch.condition = "process_healthy";
postgres.condition = "process_healthy";
};
};
frontend.exec = "cd frontend && elm-land server";
};
git-hooks = {
hooks = {
rustfmt.enable = true;
rustfmt.packageOverrides.rustfmt = config.languages.rust.toolchain.rustfmt;
nixfmt.enable = true;
elm-format.enable = true;
};
settings.rust.cargoManifestPath = "./backend/Cargo.toml";
};
}Join us on Discord if you have questions, thoughts, or suggestions.
If you find bugs, open an issue on https://github.com/cachix/devenv/issues.