Good-bye Docker, Hello Nix: Configuring a Magento 2 Development Environment with Rooter
Tags: magento2, tutorial, opensource, webdev
Published: 2024-02-20 10:50:29

## Table of Contents

- [Introduction](#introduction)
- [Requirements](#requirements)
- [Creating a Magento 2 dev environment](#creating-a-magento-2-dev-environment)
    - [1. Creating the project directory](#1-creating-the-project-directory)
    - [2. Entering in the created directory](#2-entering-in-the-created-directory)
    - [3. Creating the environment with rooter](#3-creating-the-environment-with-raw-rooter-endraw-)
    - [4. Allowing direnv to execute](#4-allowing-direnv-to-execute)
    - [5. Cloning the Magento 2 repository](#5-cloning-the-magento-2-repository)
    - [6. Starting the environment stack](#6-starting-the-environment-stack)
    - [7. Setting up the database](#7-setting-up-the-database)
- [Rooter Appendix](#rooter-appendix)
    - [The `.devenv` directory](#the-raw-devenv-endraw-directory)
    - [Versioning](#versioning)
    - [Diving deeper into the most important files](#diving-deeper-into-the-most-important-files)
      - [.env](#-raw-env-endraw-file)
      - [.envrc](#-raw-envrc-endraw-file)
      - [devenv.nix](#-raw-devenvnix-endraw-file)
      - [devenv.yaml](#-raw-devenvnix-endraw-file)
    - [Accessing the traefik dashboard](#accessing-the-raw-traefik-endraw-dashboard)
    - [Accessing the mailpit UI](#accessing-the-raw-mailpit-endraw-ui)
    - [Rooter commands](#rooter-commands)
    - [Xdebug](#xdebug)


## Introduction:

In 2023, I switched from Linux to macOS. I used Linux for years, mainly because it supported Docker natively. However, during a system update, I encountered a problem with the video driver that cost me an entire Saturday searching for solutions, tweaking files I didn't even know existed until finally, at the end of the day, I found a workaround to get the darn Nvidia driver working.

Come Monday morning, I shared my tech nightmare with a friend at work, who promptly asked:

"Why don't you switch to macOS?"

I replied: "Because it doesn't have native Docker support, and I don't want to use a virtual machine."

With a virtual machine, you have to allocate a good chunk of RAM, not to mention the slower disk processing. While disk speed might not matter much for many applications, it makes a huge difference for a Magento 2 environment.

"Why do you even need Docker?" he asked.

That sparked a quick conversation where he introduced me to [Nix](https://nixos.org/) and [devenv](https://devenv.sh/).

With these tools, it's possible to create a development environment that's entirely reproducible and native to macOS!

It was love at first sight! When I saw it in action, my mind was blown!

![shocked](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u07m8ivwe6owetxkkxce.gif)

During that conversation, I first contacted one of the early versions of [rooter](https://github.com/run-as-root/rooter).

Rooter is a powerful command-line tool to automate repetitive tasks in creating and maintaining development environments.

Shortly after, I bought a MacBook and started using `rooter` to manage my projects. Nowadays, I don't have Docker installed on my machine, and I couldn't be happier!

## Requirements

You need macOS operating system and the softwares below installed:

- [rooter](https://github.com/run-as-root/rooter)
- [nix](https://nixos.org/)
- [devenv](https://devenv.sh/)
- [direnv](https://direnv.net/)

Just follow the instructions on the `rooter` [documentation](https://github.com/run-as-root/rooter?tab=readme-ov-file#prerequisites).

Just giving a brief introduction about the these tools:

- **rooter** is a CLI tool created By [Matthias Walter](https://www.linkedin.com/in/matthiaswalter/). This tool will be your best friend during your development journey. It is similar to [Warden](https://docs.warden.dev/) or [DDEV](https://ddev.readthedocs.io/). It takes care of things like automatic DNS resolution using dnsmasq, managing project routes with [Traefik](https://traefik.io/traefik/), creating SSL certificates, and even includes handy commands for importing and exporting databases, plus a whole lot more.

- **nix** has been around since 2003 and it's super reliable. It's like a toolbox with lots of useful tools inside, such as a programming language, an operating system, and a package manager. It helps you make software systems that are consistent and easy to recreate.

- **devenv** is kind of like [docker-compose](https://docs.docker.com/compose/), but way stronger. It's like being able to use a programming language to set up your environment, giving you even more control and flexibility.

- **direnv** is a tool that figures out and loads all the stuff you need for your current project whenever you step into its folder in your terminal. We'll get to see how it works soon.

## Creating a Magento 2 dev environment

So getting straight to the point, let's set up a development environment to run Magento 2.

At this point, I'm assuming you've got `rooter` and all the necessary tools it relies on installed and ready to roll. Let's dive in!

### 1. Creating the project directory

```bash
mkdir -p ~/Sites/magento2
```

### 2. Entering in the created directory

```bash
cd ~/Sites/magento2
```

### 3. Creating the environment with `rooter`

```bash
rooter env:create
```

Rooter will ask what type of environment:

![rooter env type](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bakfqszz19t45juvrro8.png)

Type 2 and enter for Magento 2

It generated 4 files:
- `.env`
- `.envrc`
- `devenv.nix`
- `devenv.yaml`

### 4. Allowing direnv to execute

You probably saw this message in your terminal:

![dir env allow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hhq7txudw2fglyg61cj.png)

You need to allow direnv to execute.

Run the command below:

```bash
direnv allow .
```
Now you'll see something like this in your terminal:

![dir env steup](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ml35oz8pj3a8kgh57xo2.png)

In this moment, your dev stack declared in the `devenv.nix` file will be downloaded and configured. This is the longest step. Wait until the installation process finishes.

You will see something like this when the process finishes:

![direnv installation finished](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lp9vn7h1yv5tb4oqvmi4.png)

Now, please take a look into this image:

![direnv loading](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u7lcwijvehopogposj8s.gif)

If you've noticed, when I'm not in the project folder, the PHP version in the terminal is different, not 8.2. But as soon as I step into the project folder, direnv works its magic and loads the entire setup into the terminal. Pretty neat, isn't it?

### 5. Cloning the Magento 2 repository

```bash
composer create-project --repository-url=https://repo.magento.com/ magento/project-community-edition:2.4.6-p4 magento2-codebase
```

It'll clone the Magento 2 repository into a directory named magento2-codebase. Once it's done, just copy everything from the magento2-codebase folder to the current directory, and then you can delete the magento2-codebase directory with the command below:

```bash
cp -R magento2-codebase/* . && rm -Rf magento2-codebase
```

### 6. Starting the environment stack

I like to start the environment in the debug mode in the first attempt, because this shows detailed output about the initialization and in case there is an issue, it is visible right away:

```bash
rooter env:start --debug
```

Basically devenv will download all services upon first start. That could also be nginx, opensearch, rabbitmq etc.

![opensearch lib download](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bpubgow1dybsmlpudu82.png)

After the first start I like to stop the environment (CMD + c) and start it again without the debug flag:

```bash
rooter env:start
```

The reason being that now it is running in the background rather than the foreground which it was with `--debug` flag passed.

To see what is the status of the environment, run the command below:

```bash
rooter env:status
```

It will show something like this:

![env status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ebtuodopzhtw5635jxna.png)

As mentioned earlier, it uses [process-compose](https://github.com/F1bonacc1/process-compose) in the background. There is a command to attach to the process-compose and get a control panel to check the various processed that got launched.

```bash
rooter env:process-compose
```

As you can see in the image below, you can easily switch between the different services and watch their logs update live.

![Process compose](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o1k75okbrwd7anw5dcp4.gif)

### 7. Setting up the database

Run the command below to setup the database:

```bash
rooter magento2:db-install
```

Rooter will drop and recreate the database and by default run the commands below:

- `bin/magento setup:install`
- `bin/magento setup:upgrade`
- `bin/magento indexer:reindex`

An admin user will be created with the username `admin` and password `admin123`.

If you pass the option `--config-data-import` it will run the command `bin/magento config:data:import config/store dev/rooter`

**Note**: The `config:data:import` is provided by the [Magento2-ConfigImportExport](https://github.com/semaio/Magento2-ConfigImportExport) module. You need to install it to use the `--config-data-import` option flag.

You can skip the reindex by passing the option `--skip-reindex`

Run the commands below to set some extra configs:

```bash
bin/magento config:set web/unsecure/base_url https://magento2.rooter.test/ && \
bin/magento config:set web/secure/base_url https://magento2.rooter.test/ && \
bin/magento config:set web/secure/offloader_header X-Forwarded-Proto && \
bin/magento config:set web/secure/use_in_frontend 1 && \
bin/magento config:set web/secure/use_in_adminhtml 1 && \
bin/magento config:set web/seo/use_rewrites 1 && \
bin/magento config:set catalog/search/enable_eav_indexer 1 && \
bin/magento config:set dev/static/sign 0 && \
bin/magento deploy:mode:set -s developer && \
bin/magento cache:clean
```

These are the Store front and admin URLs:

- [https://magento2.rooter.test](https://magento2.rooter.test)
- [https://magento2.rooter.test/admin](https://magento2.rooter.test/admin)

Tcharaaam!

![home](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8a59fln3vusfjdvy9pma.gif)

What really blows me away is the speed. Running Magento natively on Mac makes everything super fast and smooth.

--- 

### Rooter Appendix

#### The `.devenv` directory

The `.devenv` directory contains the data about the profile, nix garbage collector, the stack state, etc.

The `.devenv/state` directory is where some of the stack code lives, as for example, `elasticsearch`, `mysql`, `php-fpm`, `nginx`, `rabbitmq`, etc.

You don't need to touch these folders or the files on it.

![.devenv directory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vrgvbrdcu62raduxrgoj.png)

#### Versioning

Which files and directories should you version or remove from versioning:

You don't need to version the files and folders below, so add them to your `.gitignore` file.

- `.devenv`
- `.direnv`
- `.env`

You need to version the files below:

- `.devenv.flake.nix`
- `.envrc`
- `devenv.lock`
- `devenv.nix`
- `devenv.yaml`

**Note:** None of this files is required by rooter. They are required from `devenv` and `direnv`.

#### Diving deeper into the most important files

##### `.env` file:

Contains all variable environments that is used in your project stack

![dot env file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ey115h4spwg2ygm5n8f.png)

`ROOTER_ENV_TYPE` defines the type of the environment, it can be:

- magento1
- magento2
- php-plan
- shopware6
- symfony

`DEVENV_HTTP_PORT` defines the http port used from the Nginx webserver

`DEVENV_HTTPS_PORT` defines the https (SSL) port used from the Nginx webserver

`DEVENV_DB_PORT` defines the Mysql/MariaDB/Percona database port

**Note:** In the current version of `rooter`, only Mysql* database is supported.

Mysql* I mean any of the versions or forks of it, for instance MariaDB, Percona, etc.

`DEVENV_MAIL_SMTP_PORT` defines the [mailpit](https://github.com/axllent/mailpit) SMTP port

`DEVENV_MAIL_UI_PORT` defines the [mailpit](https://github.com/axllent/mailpit) user interface port

`DEVENV_REDIS_PORT` the name already explains it :)

`DEVENV_AMQP_PORT` defines the [RabbitMQ](https://rabbitmq.com/) port that will be used in your application

`DEVENV_AMQP_MANAGEMENT_PORT` defines the RabbitMQ admin panel

`DEVENV_ELASTICSEARCH_PORT` defines the [ElasticSearch](https://www.elastic.co/elasticsearch) port that will be used from your application

`DEVENV_ELASTICSEARCH_TCP_PORT` the name already explains it :)

`DEVENV_PROCESS_COMPOSE_PORT` the name already explains it :)

We'll see more about the process compose later.

In case you noticed and are wondering, where the heck did the values of the ports defined in these variables come from?

Rooter has a mechanism that will get the available ports in your environment, taking into account the existing environments that were created by rooter.

It will assign ports using certain ranges, you can see how it works in [this](https://github.com/run-as-root/rooter/blob/main/src/Manager/PortManager.php) file, please look:

![rooter ports manager](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3wya2zvrod8cuniuqkis.png)

##### `.envrc` file:

![envrc file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y557ojp5jr7v8uu5btp0.png)

This file is used by the `direnv` tool.

It will load all the declared stack to the current terminal context.

##### `devenv.nix` file:

This file is used by `devenv`, it uses the [Nix language](https://nixos.org/manual/nix/stable/language/index.html) to declare the environment.

You can find more information about it [here](https://devenv.sh/basics/).

Let's get more details of the most important parts of this file:

```nix
{ pkgs, inputs, lib, config, ... }:
```

In Nix, when you see this line, it's like setting up a template for a function. It says that the function will at least get these four things: pkgs, inputs, lib, and config. But it's cool because it can also get more stuff using the `...` thingy.

```nix
let
    rooterBin = if builtins.getEnv "ROOTER_BIN" != "" then builtins.getEnv "ROOTER_BIN" else "rooter";
    composerPhar = builtins.fetchurl{
        url = "https://github.com/composer/composer/releases/download/2.2.22/composer.phar";
        sha256 = "1lmibmdlk2rsrf4zr7xk4yi5rhlmmi8f2g8h2izb8x4sik600dbx";
    };
    magerun2Phar = builtins.fetchurl{
        url = "https://github.com/netz98/n98-magerun2/releases/download/7.2.0/n98-magerun2.phar";
        sha256 = "0z1dkxz69r9r9gf8xm458zysa51f1592iymcp478wjx87i6prvn3";
    };
in {
```

The `let...in` block in the provided `devenv.nix` file defines a scope for local definitions that can be used later in the expression. This construct allows for declaring variables and their values before using them in the main body of the Nix expression. 

Here's a breakdown of the let block and its components:

- **rooterBin**: Defined based on the ROOTER_BIN environment variable. If set, rooterBin uses its value; if not, it defaults to "rooter". Enables flexible configuration.

- **composerPhar/magerun2Phar**: Downloads the `Composer PHAR` and `n98-magerun2` PHAR file from GitHub using a URL and a sha256 hash for security.

```nix
    dotenv.enable = true;
    env = {
        PROJECT_NAME = "magento2";
        PROJECT_HOST = "magento2.rooter.test";

        NGINX_PKG_ROOT = pkgs.nginx;
        DEVENV_STATE_NGINX = "${config.env.DEVENV_STATE}/nginx";

        DEVENV_PHPFPM_SOCKET = "${config.env.DEVENV_STATE}/php-fpm.sock";

        DEVENV_DB_NAME = "app";
        DEVENV_DB_USER = "app";
        DEVENV_DB_PASS = "app";

        DEVENV_AMQP_USER = "guest";
        DEVENV_AMQP_PASS = "guest";
    };
```

The `env` block declares the environment variables.

The project's domain is determined by the `PROJECT_HOST` variable. So, you can access the project using the address: `https://magento2.rooter.test`. 

If you need to change the `PROJECT_HOST` variable value, follow the instructions [here](https://github.com/run-as-root/rooter/blob/main/docs/CONFIGURATION.md#custom-tld).

It is possible to have custom subdomains, you can see more info about it [here](https://github.com/run-as-root/rooter/blob/main/docs/CONFIGURATION.md#domains-and-subdomains).

Just a heads up, the database will use as name, user, and password, all set to app by default. Feel free to tweak these settings whenever you need.

Similarly, for RabbitMQ, the default user and password are both guest. You're welcome to adjust these as necessary.

![php declaration](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3slh6c1t0cmxe1oeblyg.png)

This is where we define and set up PHP. If you ever need to switch the PHP version, simply make the change in the highlighted section shown in the image.

![Mysql](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/va57nptpoybqwh1o6ba1.png)

The same for MariaDb version. If you need a different version just change it.

To know what are the supported versions, you can search in directly in the [nix packages page](https://search.nixos.org/packages), for example:

![Mysql search](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8avwfu6izo8ss22c9g1f.gif)

**Note:** please note that we're using the `unstable` channel, it is the default channel for devenv, because it has more packages and with newer versions on it. So when you'll search the package, don't forget to select the unstable channel. 

![unstable channel](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l6ggck7a05y7oq60cs9a.png)

Last we have RabbitMQ declaration, if your project does not use it, just remove it from the `devenv.nix` file. We'll maintain it :)

![Rabbit MQ](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1b5g72lioenu2harwdm2.png)

##### `devenv.yaml` file:

This file is used by `devenv`. It will serve to declare what is the source of the packages. You can have multiple [inputs](https://devenv.sh/inputs/), using different [channels](https://nixos.wiki/wiki/Nix_channels), for example.

#### Accessing the `traefik` dashboard

To access the traefik dashboard, just type `rooter traefik:dashboard`

![traefik](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qe0yeslhhge29a9loe7i.png)

#### Accessing the `mailpit` UI

[Mailpit](https://mailpit.axllent.org/) is a fork of [mailhog](https://github.com/mailhog/MailHog), It is newer and constantly receives updates.

It is per environment it will have always the URL with this pattern:

`https://PROJECT_NAME-mail.rooter.test`

In our case it will be:

[http://magento2-mail.rooter.test](https://magento2-mail.rooter.test)

![mailpit](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/atcmny7lexvfqlnoyr0j.png)

**Note:** The `PROJECT_NAME` variable is defined in the `devenv.nix` file

#### Rooter commands

Just a heads up, to explore the existing rooter commands, you can simply run the command `rooter commands`. This will give you a list of all available commands. Happy exploring!

![rooter commands](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zx8gty5onb8b2rbo5wgq.png)

#### Xdebug

The only thing action you need to configure Xdebug is to set this environment env to your terminal:

```
export XDEBUG_CONFIG="idekey=PHPSTORM-XDEBUG"
```

You can append it with the command below:

```bash
echo 'export XDEBUG_CONFIG="idekey=PHPSTORM-XDEBUG"' >> ~/.zshrc
```

Now you're able to debug your Magento 2 store on CLI or throughout web without any configuration.

On PHPStorm for instance, you just have to click on the "Bug" button to start listening to Xdebug connections:

![Xdebug](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t1dvth5rg2tfhn3ng8dd.png)


---

If you have any specific scenarios you'd like guidance on, like setting up Magento with multiple websites or store views, feel free to leave your question in the comments below. I'll do my best to assist you!



I hope you found this helpful! 👋