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 (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 -- installHow 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 installIf 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-daemonSidenote: 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/installHow 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-daemonHow 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 devenvStep 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-unstableStep 3. Verify the Installation
After the installation is complete, verify that devenv is installed correctly by running:
devenvThen you should see something like this
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.
Assuming it’s all working at this point, you can initialize a new project with devenv using the command:
devenv initThis will take some time depending on your internet connection. If everything loaded properly, you should see something like this:
How to enter the devenv environment
After initialization is complete, enter the devenv environment with:
devenv shellThis 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 direnvHow to install and configure direnv on macOs
brew install direnvHow 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 | sourceWhat 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 allowAfter 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.
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:
.envrcdevenv.yamlDevenv.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 devenvThe 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.
To get rid of this warning, add the following line to the very bottom of the .envrc file:
unset PS1Remember though: After adding this, you need to run
direnv allowagain in your project directory like I mentioned before. This is necessary because the.envrcfile 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-unstableUnderstanding 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 nodejsIntroducing 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.
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.
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:
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-brainJust 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:
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:
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:
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, anddevenv.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!






