Streamline Your Dev Setup with devenv: A Step-by-Step Guide

Wolfgang Kreminger
Wolfgang Kreminger
hero image

Are you tired of:

  • Juggling multiple tools to set up your development environment?
  • Struggling with performance issues?
  • Spending too much time on configuration?

If so, you're not alone. Many developers start with tools like Xampp for basic needs, providing a standard Apache web server and a MySQL database.

However, as projects grow, so do their requirements. Modern web development demands advanced caching, asynchronous queuing systems, and more robust infrastructure to ensure scalable and high-performance web applications. While Docker with DDEV has been a step forward, it often falls short with performance issues for larger projects.

The good news is that there is a solution, and I’m going to share it with you in this tutorial. Enter devenv—a game-changing development environment that leverages the Nix package manager to provide a fast, declarative, reproducible, and composable environment setup.

Not only does it automatically install and maintain various software and their specific versions, but it also integrates essential technologies like Redis, Node.js, PHP, and RabbitMQ effortlessly. Pretty good, right?

In this guide, I’ll walk you through:

  • A basic understanding of the tools necessary to get started
  • How to set up a ready-to-use devenv setup for your future projects
  • The directory structure of a devenv project
  • Example setups for JavaScript and PHP projects to plug and play

So let's get into it.

What is devenv?

devenv

devenv (short for ‘Developer Environment’) is a fast, declarative, reproducible, and composable tool that leverages the Nix package manager to create efficient and consistent development environments.

What is Nix?

Nix is a purely functional package management system. Each package in Nix is self-contained and only available within the defined Nix environment once added.

For example

If you install Node.js on your computer, it typically becomes available system-wide. However, within a Nix environment, Node.js is isolated and accessible only inside that environment, preventing any side effects on your global system.

Advantages of Nix compared to Docker and VMs

  • Performance: No need to sync source code inside containers, eliminating performance lags
  • Configuration Experience (DX): Uses a JSON-like schema for each package, making configuration straightforward and user-friendly
  • Accessibility: No need to SSH/connect into a container via interactive shells to access files or binaries like Node.js
  • Up-to-date Packages: The latest package versions are available out of the box unless configured otherwise
  • Service Management: Provides an overview of different services and their logs directly inside the terminal

How to install Nix (so that devenv can work)

As I mentioned earlier, devenv depends on Nix to work. Don’t worry though as I’ll walk you through the process of installing all necessary packages step by step now.

Just be sure to follow each step carefully to avoid any issues.

The first step is to install the underlying Nix packages. However, because there is not one single OS out there, please use the command for your own OS from below. (I’ve added a few options based on whatever you use).

How to install Nix on Linux

curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install

How to install Nix on macOS

Because we have two possible macOS, or better said, two different hardware types out there, I would recommend using this command:

curl -L https://raw.githubusercontent.com/NixOS/experimental-nix-installer/main/nix-installer.sh | sh -s install

If you don't have one of the fancy Apple Silicon chips, then you can use this one:

sh <(curl -L https://nixos.org/nix/install)

How to install Nix on Windows (WSL2)

Important: Heads up to Windows users! devenv does not work natively on Windows due to its reliance on the Linux package system.

So, to use devenv on Windows, you need to set it up within your WSL2 (Windows Subsystem for Linux 2) environment.


While this might seem like an extra step, it's worth it since most web projects are hosted on Linux servers. And if you're not using WSL2 yet, then now is a great time to start!

Once you have that setup, here’s the code to install Nix:

sh <(curl -L https://nixos.org/nix/install) --no-daemon

Sidenote: OhMyZsh users

If you are using OhMyZsh, you might need to make some additional configurations in your ~/.zshrc file. Refer to this Stack Overflow answer for detailed instructions.

How to boost installation performance with cachix

Installing devenv and its dependencies can sometimes be time-consuming, especially if you have to build packages from scratch.

Cachix, (the same team behind devenv), offers a solution to this problem by providing a cache store of pre-built packages. This means you can significantly reduce installation times, making your setup process faster and more efficient.

In order to install cachix, run

nix-env -iA cachix -f https://cachix.org/api/v1/install

How to configure trusted users on Cachix

If you are using Cachix for the first time (which you probably are), you will also have to add your user to the trusted store to let Cachix do its work.

echo "trusted-users = root ${USER}" | sudo tee -a /etc/nix/nix.conf && sudo pkill nix-daemon

How to install devenv

Now that you've set up Cachix to boost installation performance, you're ready to install devenv.

Step 1. Configure Cachix to use the devenv cache

First, instruct Cachix to use the stored devenv cache from the Cachix store:

cachix use devenv

Step 2. Install devenv

With Cachix configured, you can now install devenv by running:

nix-env -iA devenv -f https://github.com/NixOS/nixpkgs/tarball/nixpkgs-unstable

Step 3. Verify the Installation

After the installation is complete, verify that devenv is installed correctly by running:

devenv

Then you should see something like this

devenv command executed

If you get an error message that devenv is not recognized as an internal or external command, or something similar, restart your terminal by closing it entirely and opening it again.

tech support

Assuming it’s all working at this point, you can initialize a new project with devenv using the command:

devenv init

This will take some time depending on your internet connection. If everything loaded properly, you should see something like this:

devenv init command

How to enter the devenv environment

After initialization is complete, enter the devenv environment with:

devenv shell

This step is necessary to work inside the devenv sandbox environment and execute commands like node -v if you have added it.

How to simplify activation with direnv

I know what you’re thinking. I promised in the title that using devenv is a much more painless process, yet it seems like there are a lot of steps so far, right? Well, good news—there’s a way to streamline this.

We can use direnv to automatically activate the devenv environment whenever you enter the project directory, saving you from manual activation each time.

There is a slight caveat: you need to add the project directory to the allow list, but I’ll explain how to do this shortly so that you can access all the binaries straight away.

How to install and configure direnv

How to install direnv on Linux (Ubuntu and all distros which use apt)

Let's install direnv by running:

apt install direnv

How to install and configure direnv on macOs

brew install direnv

How to install and configure direnv on other operating systems

If the above commands don't work for you, refer to the official installation page.

How to add a direnv hook to your shell profile

To automatically load the environment, add the direnv hook to your shell profile.

With Bash

// ~/.bashrc
eval "$(direnv hook bash)"

With ZSH

// ~/.zshrc
eval "$(direnv hook zsh)"

With Fish

// ~/.config/fish/config.fish
direnv hook fish | source

What to do if my shell is not listed?

If your shell is not listed, refer to the direnv hook page for instructions.

How to allow direnv to activate the environment

For security reasons, direnv does not automatically activate all environments it encounters. This is a beneficial feature, however, we do want to enable it for our specific project to streamline our workflow.

So here’s how to get around this. You have to allow direnv to activate the environment automatically by cd-ing into your devenv project directory and running:

direnv allow

After you run this command inside the directory, it will get automatically activated if you cd into it in the future.

Handy right?

Alternative option: Use a direnv plugin for VS Code

If you prefer not to set this up manually, you can use a VS Code extension instead that simplifies the process. The direnv for VS Code extension automatically activates the shell within the terminal in VS Code, enhancing your development experience without additional manual setup.

Anyways, back to this setup.

Exploring what devenv init created

Phew, that was quite a journey to get here! I know, I know, I promised a painless setup. But while the initial installation of all necessary components might seem daunting, trust me, it gets much easier from here.

Now that you've made it through the setup, future setups will be much simpler and quicker.

So far, we have a barebones project directory with just the boilerplate code created by devenv init. Let’s take a closer look at what we have and understand why these files are important.

devenv project structure

Why do this?

Simply because inspecting these files will help you understand how your development environment is configured, how to customize it to fit your needs, and how devenv ensures consistency and reproducibility across different projects and team members.

So let’s take a look at what we have.

Everything within the .devenv directory consists of internal files that devenv needs. These include all installed packages, and you can leave everything in this directory as it is, as there’s no need to modify anything here.

Likewise, the .direnv directory contains profiles created by direnv, and again, you don’t need to worry about these files.

The files we are most interested in are:

  • .envrc
  • devenv.yaml
  • Devenv.nix

So let’s get into these.

.envrc explained

This file contains instructions for the environment. If you open it, you’ll see two key instructions:

 source_url "https://raw.githubusercontent.com/cachix/devenv/d1f7b48e35e6dee421cfd0f51481d17f77586997/direnvrc" "sha256-YBzqskFZxmNb3kYVoKD9ZixoPXJh1C9ZvTLGFRkauZ0="

use devenv

The first line tells direnv to use the configured devenv cache store from Cachix, which we set up during the installation process, while the second line instructs direnv to use devenv here and activate the shell.

Sidenote: You might encounter a warning related to PS1 when using direnv.

direnv ps1 error

To get rid of this warning, add the following line to the very bottom of the .envrc file:

unset PS1

Remember though: After adding this, you need to run direnv allow again in your project directory like I mentioned before. This is necessary because the .envrc file controls direnv, and for security reasons, you need to re-authorize any changes.

Once you've allowed it again, you won’t see the PS1 error the next time you cd into the project directory. Additionally, you can add environment variables to this file that might only be needed for the environment.

Devenv.yaml explained

This file configures from which package repository packages are fetched.

By default, it uses the Nix package repository, which is generally sufficient. However, some packages, like redis-commander, may not be "free" to use in all cases due to specific licenses. To handle such cases and prevent errors, you can adjust the devenv.yaml file like this:

allowUnfree: true # <== add this line
inputs:
  nixpkgs:
    url: github:NixOS/nixpkgs/nixpkgs-unstable

Understanding and customizing devenv.yaml allows you to manage your dependencies more effectively and avoid potential licensing issues.

You can also add additional configurations here. For a detailed list of possible configurations, refer to the yaml-options documentation.

devenv.nix explained

Finally, we come to the heart of your project setup: the devenv.nix file. This is where you will spend most of your time, as it contains the configuration for which packages you want to use and how each installed package is configured.

By customizing the devenv.nix file, you can tailor your development environment to fit the specific needs of your project, ensuring that all necessary tools and dependencies are correctly configured and easily shareable with your team.

Since we’ve covered a lot of theoretical aspects, let's dive into the devenv.nix file with hands-on examples to really start setting up our environments.

Most important commands (devenv up, devenv gc)

There are several common commands you'll use frequently with devenv. Let's explore the most important ones and understand their benefits.

devenv gc

I recommend running devenv gc periodically, as this command cleans up and deletes unneeded builds of packages that are no longer required.

For example

If you change the version of a package like Postgres in your devenv.nix file, you won't need the binaries of the old version anymore. And so by running devenv gc, you can properly remove these outdated binaries, freeing up space on your hard drive.

devenv up

The devenv up command is crucial because it starts the services defined in your devenv.nix file, and ensures that all the specified services are running and ready for your development work.

If your project only includes languages and tools like Node.js, you might not need to run this command. However, if your project requires services like Postgres, Caddy, or Nginx, running devenv up is necessary to boot up these services.

devenv search

The devenv search command is incredibly useful for finding available packages and their versions.

For example

If you want to use a specific version of a package, such as Node.js, but are unsure of the available versions, you can run:

devenv search nodejs
devenv search command nodejs

Introducing the Process-Compose Interface

After running devenv up, you’ll interact with a terminal interface that helps you manage your services. This interface shows all the services that have started, their status, and how long they have been running.

devenv up process compose interface

Using the arrow keys, you can navigate through each service to view their logs, making it easier to troubleshoot and manage the various components of your environment. You can even restart any service that has stopped by hitting CTRL+R.

IMPORTANT: Leave this interface running in your terminal. If you hit CTRL+C, the services will stop and will no longer be accessible. If you need to use the terminal for other tasks, open a new terminal window.

This interface is essential for effectively monitoring and maintaining the services in your development environment, providing a clear overview and easy management tools.

Speaking of which, let’s get into some examples of this in action.

Example for a JavaScript project

Let's create an environment for a new JavaScript project using this tool. We'll start by clearing everything in the devenv.nix file since we'll be building from scratch.

As an example, let's set up an environment for the SmartBrain project from the Complete Web Developer and Complete Junior to Senior Web Developer Roadmap courses.

smartbrain

For a basic JavaScript environment setup, we need:

  • Node.js
  • PostgreSQL

Creating this environment is straightforward. If you need to switch Node.js versions, you can simply change the version number in the devenv.nix file.

Here is the final devenv.nix file for setting up the JavaScript environment for our SmartBrain project, along with some of my own comments to explain each line of the nix file:

{ pkgs, lib, config, ... }:

{
  dotenv.disableHint = true; # This line tells devenv to disable a hint in the terminal

  languages.javascript = {         # Starting from here, we define what we want for our javascript language
    enable = true;                 # enable means, we enable the configuration and language
    package = pkgs.nodejs_latest;  # We tell devenv to use the latest LTS version from NodeJs
  };

  services.postgres = {            # Start of the configuration of our postgres service
    enable= true;                  # enable postgres to boot up and to be available
    initialDatabases = [           # We want to initialize postgres with another table which is a array of tables
      {
        name= "smart-brain";       # the additional table we want should be called smart-brain
      }
    ];
  };
}

After running devenv up in the terminal, we should see something like this:

devenv up javascript project

Did you see that? We got our PostgreSQL service and our smart-brain database created. This service will also create another database with your username which you don't have to use.

If you start the services again with devenv up, the database won't get overwritten or newly created because devenv is smart enough to know that they are already there. (As a side note, they are stored at path/to/your/project/root/.devenv/state/postgres).

The beauty now is that we can simply use the psql command within the devenv project directory even though we don't have it installed globally on our system.

Give it a try, open another terminal window, and type:

psql -U <yourSystemUserName> smart-brain

Just make sure that <yourSystemUserName> is your real username which you have on your operating system so that it works correctly.

The password is empty by default so you should see immediately:

devenv psql smart-braun executed

Everything which you now need are the source files which you can simply place within path/to/your/project/root.

Example setup for a PHP project

A PHP project setup is not much more complicated than the JavaScript example. Let's create a development environment for the PHP Development Bootcamp: Zero to Mastery course.

We will need:

  • PHP
  • A web server
  • MySQL
  • A graphical database management tool
  • Optionally, JavaScript

Here is the final devenv.nix file for setting everything up:

  { pkgs, lib, config, ... }:

  {
    dotenv.disableHint = true;

    # add NodeJs just in case you want to use it
    languages.javascript = {
      enable = true;
      package = pkgs.nodejs_latest;
    };

    # Configure php and add some basic php.ini settings to it
    languages.php = {
      enable = true;
      version = "8.3";
      extensions = [];

      ini = ''
        memory_limit = 512M
      '';

      # Configure php fpm pools for future use
      fpm.pools.web = {
        settings = {
          "pm" = "dynamic";
          "pm.max_children" = 5;
          "pm.start_servers" = 2;
          "pm.min_spare_servers" = 1;
          "pm.max_spare_servers" = 5;
        };
      };
    };

    # Enable the Web Server of choice
    # In this case I used caddy for the development environment because it is
    # easier to configure and faster.
    # Keep in mind, that the .htaccess file won't work with caddy because it is
    # an apache server only feature.
    services.caddy.enable = true;

    # add our virtual host like in the course
    services.caddy.virtualHosts."http://phpiggy.local:8000" = {
      extraConfig = ''
        root * public
        php_fastcgi unix/${config.languages.php.fpm.pools.web.socket}{
            trusted_proxies private_ranges
        }
        file_server
        encode

        encode zstd gzip
      '';
    };

    # add mysql
    services.mysql = {
      enable = true;
      # here you can change the mysql version just in case
      package = pkgs.mysql80;
       # we give the service a input of databases which we want to create for us on boot up
      initialDatabases = [{ name = "phpiggy"; }];
      # here we add a user with a password and give the user the permission
      # to access with ALL PRIVILEGES the phpiggy database
      ensureUsers = [
        {
          name = "phpiggy";
          password = "changeMe";
          ensurePermissions = {
            "phpiggy.*" = "ALL PRIVILEGES";
          };
        }
      ];

      # some recommended settings which mysql should have set
      settings = {
        mysqld = {
          group_concat_max_len = 320000;
          log_bin_trust_function_creators = 1;
          sql_mode = "STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION";
        };
      };
    };

    # add graphical database tool for managing our database
    services.adminer.enable = true;
  }

After adding this devenv.nix file to your project and running devenv up, you should see in your terminal that all services have started properly:

devenv up php project

As you can see, I didn't use an Apache web server or phpMyAdmin. This is because while these tools are effective, performance matters in a development environment, and we can do better.

The Caddy server is written in Go and is very fast, with an intuitive configuration file, while Adminer, compared to phpMyAdmin, is more lightweight. It can do the same tasks as phpMyAdmin with less overhead

You will need to manually add phpiggy.local to your hosts file as shown in the PHP course if you’re testing this with my PHP example.

To use this yourself:

  • The host is localhost
  • Username is phpiggy
  • Password is changeMe, and
  • The database is phpiggy

Here is an overview of how each service is accessible:

overview

Time to try this out for yourself

Hopefully this comprehensive overview of how to use devenv to build your development environment has inspired you to try this yourself.

With just a few lines of configuration, you can:

  • Recreate your entire production environment within a single file with native running services
  • Adding new services is as easy as writing a few lines of code, and you're set up
  • While sharing the infrastructure setup with collaborators and teammates is as simple as sharing three files: .envrc, devenv.nix, and devenv.yaml, and cloning the source code of your project into it
  • And by simply running devenv up, you can start working immediately without further setup

Don’t get me wrong. Traditional methods like Xampp and Docker have their merits but also drawbacks:

  • Xampp is easy to set up but lacks flexibility and scalability
  • While Docker is powerful but can suffer from performance issues and complexity with larger projects

Devenv offers a more streamlined approach with fast, declarative, reproducible, and composable environment setups using the Nix package manager, addressing many of these issues.

So if you haven’t already, now is the perfect time to try out devenv. Its ease of setup and powerful capabilities could be the game-changer you need in your development workflow.

P.S.

If you enjoyed this tutorial, be sure to check out my development blog. You can also connect with me on LinkedIn to stay updated with my latest work and insights.

Otherwise, I’ll see you in the ZTM Discord, so be sure to ask me questions there also.


Thanks for reading!

More from Zero To Mastery

How To Become A Web Developer (From Complete Beginner to Hired) preview
How To Become A Web Developer (From Complete Beginner to Hired)

Want to become a Web Developer but not sure how? Our step-by-step guide shows you how to go from beginner to hired (without wasting $1,000s on a bootcamp or degree). And we answer all your FAQ.

Starting Over At 56: A Journey From Consulting To Coding preview
Starting Over At 56: A Journey From Consulting To Coding

Discover how one ZTM student switched from consulting to coding, navigating the challenges of a late-career change - to achieve success in web development.

What’s The Best Way To Learn To Code? preview
Popular
What’s The Best Way To Learn To Code?

Coding Bootcamp vs Degree vs Self-Taught? There are pros & cons of each route. This deep dive breaks it all down and will help you find the best option for YOU.