# Environment

Source: https://modulify.ai/docs/editor/environment

Add system packages and setup commands to the machines your preview and live site run on. Paid plans only.

Every site runs on the Modulify base: Node.js 22 on Debian 12, with the basic tools a site needs. The **Environment** tab adds lines on top of that base, such as system packages like `ffmpeg`, `chromium` or `libreoffice`, or a setup command. Modulify builds them into the machines your preview and your live site run on, and into the machine that builds your site when you publish, so your site's code can call those programs while it runs and while it builds.

Environment needs a paid plan. Any member of the workspace with the Editor [role on the site](https://modulify.ai/docs/editor/settings#member-access) can change it.

> **Not the same as Secrets**
>
> Values your site reads at runtime, such as API keys, live in [Secrets](https://modulify.ai/docs/data/secrets). The Environment tab is about the programs installed on the machine.

## Before you begin

Check whether you need it at all. Many npm packages ship their own program, `sharp` for images for example, and those work without an environment. Reach for the Environment tab when your site needs a system program or library that npm cannot provide.

An environment adds programs, not running services. It cannot start a database server or a background process next to your site, or open another port. Your site still starts the way it does without an environment.

## Open the Environment tab

Click **More** in the tab strip, then **Environment**. The address ends in `/environment`.

Next to the **Environment** title a badge shows where things stand. Hover it for a one line explanation:

| Badge | What it means |
| --- | --- |
| `Standard` | The site runs on the standard environment: no lines are saved, the workspace is on the Free plan, or the environment is turned off for the site. |
| `Queued` | Your save is waiting for a build to start. |
| `Building` | The build is running. Your preview keeps its current environment until the build works. |
| `Ready` | The build worked. Your preview runs it, or moves to it shortly. |
| `Failed` | The build failed and nothing changed. The line that failed is marked, or the message says why. |
| `Canceled` | The build was skipped before it ran, for example because the workspace left its paid plan. Nothing changed. |
| `Replaced` | A newer save took its place, and your preview moves to that one instead. |

Once the site is published, a line at the top of the tab says whether your live site has caught up: `Your live site runs this environment.`, or `Your live site still runs its previous environment until you publish.` with a **Publish** button next to it.

**Save and build** sits on the right of the title row. The menu next to it holds **View as Dockerfile**, **View build log** once a build has worked or failed, **Undo** and **Reset to standard**.

## The lines

The tab lists your environment top to bottom, in the order it is built:

| Row | What it does |
| --- | --- |
| **Modulify base** | Locked. Node.js 22 on Debian 12, which Modulify keeps up to date. |
| **Install packages** | Installs Debian 12 packages with apt. Type their names separated by spaces, up to 50 on one line. |
| **Run command** | Runs one shell command as root while the machine image is built, up to 2,000 characters. The line fails as soon as any part of it fails. |
| **Modulify setup** | Locked. Runs your site as `appuser` and starts it, with Modulify's own files kept as they are. |

Use **Add line** to add an **Install packages** or a **Run command** row, and the menu on a row to **Move up**, **Move down** or **Delete** it. A site holds up to 20 lines on a paid plan. Under the list the tab shows how many lines you use and how many saves are left today.

A **Run command** line covers what apt alone cannot do. Every line is built twice, once for your preview and once for your live site and its publish build. Both start with the same tools: Python 3, `curl`, `make` and `g++`, but not `pip` or `gnupg`. Install whatever a command uses in an **Install packages** line above it. A line cannot use or install `git`, because your preview keeps it turned off.

For a Python package, prefer the Debian package when there is one, such as `python3-pandas`. Otherwise add `python3-pip` in an **Install packages** line first, then a **Run command** line like this one, because Debian's own Python refuses a plain `pip install`:

```bash
pip install --break-system-packages pandas
```

## Save and build

### Add your lines

Add an **Install packages** line with the package names, or a **Run command** line with the command.

### Press Save and build

The badge shows `Queued`, then `Building`. A build usually takes a few minutes, and one that runs longer than 20 minutes is stopped.

### Your preview moves to it

When the build works the badge reads `Ready`. Your files are saved first, then your preview restarts on the new environment and shows `Moving preview to its new environment...` while it switches. It never restarts in the middle of a chat reply: it waits until the reply ends. On a site with [several chats](https://modulify.ai/docs/build/multiple-chats) it also waits while a chat has changes that are not saved yet, and while it switches, a message sent to any chat waits in its queue with `Waiting while the site finishes a restore or move.` until the switch is over, then starts by itself.

Modulify then checks that your preview starts on the new environment. If it does not start within a few minutes, your preview goes back to the environment it had, and the build shows as `Failed` with the message `Your preview did not start on this environment, so it went back to the one it had.`

### Publish

Your live site gets the environment the next time you [publish](https://modulify.ai/docs/publish/publish-a-site). Until then it keeps the one it had. If a build is still running when you publish, the publish waits for it to finish first, so its first step takes longer.

Wherever you are in the project, a notice tells you when a build you saved is ready or has failed, with **Open** to come back to this tab.

Saving the same lines again does nothing while they are queued, building or built. A site can save its environment 20 times in 24 hours, and past that **Save and build**, **Undo**, **Try again** and **Reset to standard** are disabled with the tooltip "This site has used all of its saves for today." Saving while a build is running is fine: the newer save is built too, and the older one is not used, even if the newer one fails.

## When a build fails

A build that fails changes nothing. Your preview and your live site keep the last build that worked. The badge reads `Failed`, the line that failed is marked, and the message says why, for example:

- `Line 2: Debian 12 has no package named ffmpg. Check the spelling, or look up the right name on packages.debian.org.`
- `Line 3 stopped with an error. The last lines it printed show why.`, with those lines under it.
- `The build ran longer than 20 minutes, so it was stopped. A line may be waiting for an answer or downloading something very large.`

When a line works for your preview but not for your live site, the message ends with `It worked for your preview, but not for your live site and publish build.`

Fix the line and press **Save and build** again. With the message:

- **Fix with AI** puts the error into the chat, so the AI can fix the line for you.
- **Try again** builds the same lines again. It shows when the build stopped for a reason outside your lines: Modulify could not build it this time, as in `Modulify could not build the environment this time. Try again in a few minutes.`, the build was interrupted, or it ran out of time.

**View build log**, in the menu next to **Save and build**, shows the whole output in three parts, the preview image, the publish build image and the live site image, all built from the same lines.

Some lines fail because of what they change rather than what they print. The build checks that a line did not replace or remove something Modulify needs to run your site, and fails with a message naming it:

- Node.js or npm, which stay at the versions in the base
- the `appuser` account your site runs as
- `setpriv`, which starts your site safely
- `git` in your preview, which stays turned off because Modulify saves and versions your files for you
- one of the tools Modulify uses: `sh`, `bash`, `curl`, `git`, `tar`, `gzip` or `sha256sum`
- the system certificates your site needs to connect securely

Your lines can add up to 2 GB on top of the base. A build that adds more fails, with the size it reached in the message.

## What stays locked

These hold whatever your lines do:

- **The base.** Node.js 22 on Debian 12. You cannot pick another distribution or another Node.js version.
- **Modulify's files.** A command cannot change the files in `/app`. Anything a line writes there is thrown away.
- **No root while the site runs.** Your preview and your live site run as `appuser`, with no root and no sudo. Setuid permissions are removed from every file, so nothing a line installs can be used to become root.
- **How the site starts.** The **Modulify setup** line starts your site the same way as without an environment.

Your live site runs on a small machine. A program that needs a lot of memory, such as a headless browser, can run out of it there even when it works in the preview.

## View as Dockerfile

**View as Dockerfile**, in the menu next to **Save and build**, shows the file Modulify builds your preview from, with line numbers. Your lines are the `RUN` steps in the middle. Before them is the base, shown as `modulify/preview-base`. After them comes the check step that looks for the problems above, and the setup that starts your site. Your live site and its publish build are built from the same lines on top of Modulify's build base. The file shows the saved lines, so save first to see a change there.

Each of your lines runs with `/app` replaced by an empty folder, which is why nothing a line writes there is kept:

```dockerfile
RUN --mount=type=tmpfs,target=/app ["/bin/sh","-c","set -e\necho MODULIFY_LINE 1\napt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends ffmpeg\nrm -rf /var/lib/apt/lists/*"]
```

## Undo and reset

Both are in the menu next to **Save and build**.

- **Undo** goes back to the last environment that worked and builds its lines again. When your newest save failed or is still building, that is the environment your site uses now; otherwise it is the newest earlier build that worked, or the standard environment when there is none. It asks first with "Go back to the previous environment?". When those lines were built before on the same base, the switch is quick.
- **Reset to standard** removes every line. It asks first with "Reset to the standard environment?". Your preview moves back to the standard environment shortly, and your live site with its next publish.

## When Modulify updates the base

When Modulify updates its base, it rebuilds your environment on the new base in the background, with the same lines. Your preview moves to the rebuilt environment once nobody has the project open, and your live site gets it with its next publish. If the rebuild fails, the site keeps the environment it has.

## When the plan changes

On the Free plan the tab shows your lines, but every control is disabled with the tooltip "A custom environment needs a paid plan." and saving is refused with `A custom environment needs a paid plan!` A workspace that moves to Free keeps its lines, but stops using them: the preview goes back to the standard environment the next time Modulify replaces its machine, usually within a day, and the live site with its next publish. Moving back to a paid plan brings the environment back the same way. The workspace owner changes the plan on the **Plans** page, see [Plans](https://modulify.ai/docs/plans/plans).

## What the AI can do

On a paid plan the AI in the editor chat has every control of the tab:

- read the environment, how its last build went, which build the site uses and its Dockerfile
- add, change or remove lines, undo the last change, build the same lines again after a failure, and put the site back on the standard environment
- wait for a build to finish, for example before it publishes so your live site gets it
- read the build log of any build

So you never have to add a line in the tab yourself when you ask chat for a feature that needs one. It adds a system package only when your site needs one that npm cannot provide, and asks you first before it saves a change or undoes one, unless your message asked for exactly that change. It saves on top of the lines it read, and when they changed in the meantime, for example because another chat saved first, its save is refused and it reads them again instead of overwriting them. It never installs system packages any other way. Once a build works, your preview moves to it after the reply ends, so chat cannot try new packages in the preview in the same reply that adds them. When it needs the build to go on, for example to publish, it waits for it; otherwise a notice tells you when the build is ready. When the last build failed, the AI sees it on your next message and offers to fix it. On Free it tells you the environment needs a paid plan.

## From an AI client over MCP

Everything on this page is reachable from a connected AI client through 4 tools:

- `get_site_environment` and `get_site_environment_log` read the environment, its Dockerfile and the build logs, and need `config:read`
- `update_site_environment` changes the lines and `undo_site_environment` goes back to the last environment that worked, and both need `config:write`

To follow a build, read `get_site_environment` again every 10 to 15 seconds while `Building` is true. The same tools are also methods of the [HTTP API](https://modulify.ai/docs/api).

### Can I use my own Dockerfile?

No. You add lines, and Modulify writes the Dockerfile around them, so the base and the setup that starts your site stay intact. **View as Dockerfile** shows the result.

### How do I use a headless browser?

Add an **Install packages** line with `chromium` and `fonts-noto-core`, add `fonts-noto-cjk` too for Chinese, Japanese or Korean pages, then use `playwright-core` or `puppeteer-core` and launch the browser from `/usr/bin/chromium`. Playwright's and Puppeteer's own browser downloads save the browser where your site cannot read it, so they do not work in a **Run command** line. A browser needs a lot of memory, so it can run out of memory on the live site.

### Why does pip refuse to install a package?

Debian 12's Python only takes packages from apt by default, and neither base comes with pip. Install the Debian package, such as `python3-pandas`, or add `python3-pip` in an **Install packages** line and use `pip install --break-system-packages` in a **Run command** line below it.

### Does a cloned site keep its environment?

No. A clone starts on the standard environment. Add the lines again in the clone.

### Does the environment make my site slower?

Not while it runs. Your machines download the bigger image when they start fresh, so the first start after a publish, and the publish build itself, can take a little longer.

## Next

- [Secrets](https://modulify.ai/docs/data/secrets) holds the values your site reads at runtime.
- [Publish a site](https://modulify.ai/docs/publish/publish-a-site) is how your live site gets a new environment.
- [Logs](https://modulify.ai/docs/editor/logs) shows what your preview and your live site print while they run.
- [MCP tools](https://modulify.ai/docs/mcp/tools#environment) covers the environment tools in full.