Are you tired of:
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:
So let's get into it.
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.
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.
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).
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
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)
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
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.
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
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
Now that you've set up Cachix to boost installation performance, you're ready to install devenv.
First, instruct Cachix to use the stored devenv cache from the Cachix store:
cachix use devenv
With Cachix configured, you can now install devenv by running:
nix-env -iA devenv -f https://github.com/NixOS/nixpkgs/tarball/nixpkgs-unstable
After the installation is complete, verify that devenv is installed correctly by running:
devenv
Then 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 init
This will take some time depending on your internet connection. If everything loaded properly, you should see something like this:
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.
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.
Let's install direnv
by running:
apt install direnv
brew install direnv
If the above commands don't work for you, refer to the official installation page.
To automatically load the environment, add the direnv hook to your shell profile.
// ~/.bashrc
eval "$(direnv hook bash)"
// ~/.zshrc
eval "$(direnv hook zsh)"
// ~/.config/fish/config.fish
direnv hook fish | source
If your shell is not listed, refer to the direnv hook page for instructions.
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?
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.
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:
.envrc
devenv.yaml
Devenv.nix
So let’s get into these.
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.
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.
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.
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.
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
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.
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:
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-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:
Everything which you now need are the source files which you can simply place within path/to/your/project/root
.
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:
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:
localhost
phpiggy
changeMe
, andphpiggy
Here is an overview of how each service is accessible:
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:
.envrc
, devenv.nix
, and devenv.yaml
, and cloning the source code of your project into itdevenv up
, you can start working immediately without further setupDon’t get me wrong. Traditional methods like Xampp and Docker have their merits but also drawbacks:
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.
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!