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-workshop
Projects 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 init
This 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 shell
Running tasks devenv:enterShell
Succeeded devenv:enterShell 10ms
1 Succeeded 10.47ms
hello from devenv
git version 2.44.0
Lets 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/pre-commit-hooks/
- # pre-commit.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 nix-community/fenix under the hood.
devenv will prompt you do add it as an input to your devenv.yaml
.
You can do so throught the command-line:
devenv inputs add fenix github:nix-community/fenix --follows nixpkgs
Note
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 up
devenv
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-migrations
Note
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.elmPackages.elm-land
+ ];
+
+ processes.backend.exec = "cd backend && cargo watch -x run";
+ processes.frontend.exec = "cd frontend && elm-land server"
Tip
For macOS users, the Rust backend requires a few macOS frameworks.
pkgs.elmPackages.elm-land
+ ]
+ # For macOS machines
| ++ lib.optionals pkgs.stdenv.isDarwin [
+ pkgs.darwin.CF
+ pkgs.darwin.Security
+ pkgs.darwin.configd
+ pkgs.darwin.dyld
+ ];
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-token
or if you have gh
installed:
export GITHUB_TOKEN=$(gh auth token)
Let's try it out:
flakestry-publish --owner nixos --repo nixpkgs --version 24.05
{ pkgs, lib, config, inputs, ... }:
{
dotenv.enable = true;
packages =
[
pkgs.openssl
pkgs.cargo-watch
pkgs.elmPackages.elm-land
pkgs.sqlx-cli
]
# For macOS machines
++ lib.optionals pkgs.stdenv.isDarwin [
pkgs.darwin.CF
pkgs.darwin.Security
pkgs.darwin.configd
pkgs.darwin.dyld
];
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";
};
pre-commit = {
hooks = {
rustfmt.enable = true;
rustfmt.packageOverrides.rustfmt = config.languages.rust.toolchain.rustfmt;
nixfmt-rfc-style.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.