From 0a987546bd2dc004251ab3e78c6195a87e056a4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Fern=C3=A1ndez=20L=C3=B3pez?= Date: Tue, 18 Nov 2025 14:25:03 +0100 Subject: [PATCH 1/3] feat: document agent images --- docs/agent-images.md | 203 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 docs/agent-images.md diff --git a/docs/agent-images.md b/docs/agent-images.md new file mode 100644 index 00000000..39844697 --- /dev/null +++ b/docs/agent-images.md @@ -0,0 +1,203 @@ +# Agent images + +Agents in Rover run in isolated environments, as much as possible. The +idea behind this strategy is that the agent can make as many changes +as it needs in order to improve, implement, build, check, test... on a +given project, without impacting the host machine. + +When Rover runs, it is possible to choose what image will be used to +spawn the agent in. This gives flexibility to have specific OS's +(e.g. Alpine, Debian, Ubuntu), as well as some core tooling already +installed for the project, what will increase development speed, +because the agent won't need to install new packages. + +## Rover provided agent images + +### Development images + +The code at the `main` branch points to +`ghcr.io/endorhq/rover/agent-dev:latest`. Automation builds and pushes +to this image when a new commit is pushed to the `main` branch. + +### Tagged images + +Whenever we create a new tag in Rover, a new image will be pushed with +that tag name at: `ghcr.io/endorhq/rover/agent:`. + +## Using your own agent image + +### Minimum image requirements + +The minimum requirements for the image follows: + +#### Node + +Regardless of the base image you use, Node 24 is a prerequisite, as +[`rover-agent`](https://github.com/endorhq/rover/tree/main/packages/agent) +is a Node application. + +#### Package Manager MCP + +The [package-manager +MCP](https://github.com/endorhq/package-manager-mcp) is a static +binary that allows the configured agent to search and install packages +in the container. + +It is expected that a binary named `package-manager-mcp-server` exists +in the `$PATH`. It will be configured during the agent set up phase. + +You can find the static binaries in the [Releases +list](https://github.com/endorhq/package-manager-mcp/releases). + +#### Reserved directories + +Reserved directories may or may not exist in the container +image. However, their original contents won't be available during +Agent execution, as host paths will be mounted in these directories +automatically by Rover. + +- `/workspace`: mountpoint used for the user project. + +- `/output`: mountpoint used by Rover to follow the task progress. + +#### Sudo + +`sudo` needs to be available. The Rover agent CLI goes through two +main steps: + +1. Setting up the environment and installing system dependencies +2. Running the chosen agent + +Ideally, there should be two `sudo` profiles: + +- `/etc/sudoers.d/1-agent-setup` +- `/etc/sudoers.d/2-agent-cleanup` + +The contents of this files will depend on the default groups present +in the base image to be configured for Rover. However, a good rule of +thumb is to take `/etc/group` from the base and configure both +accordingly, adding an extra group `agent` that will be created +automatically by Rover if necessary. + +Rover will remove `/etc/sudoers.d/1-agent-setup` before handing +control to the agent. From that point on, the +`/etc/sudoers.d/2-agent-cleanup` will determine what the agent is able +to do with `sudo`: it is highly recommended to reduce the list of +commands that could be executed with root permissions without +password. + +An example for `node:24-alpine` follows: + +
+ +/etc/sudoers.d/1-agent-setup + +# Rover agent group; if there is no matching gid within the container + +# with the host gid, the `agent` group will be used. + +%agent ALL=(ALL) NOPASSWD: ALL + +# Original image group list at /etc/group. If the host user gid + +# matches with any of them, it will be able to use `sudo` normally + +# within the container. + +%root ALL=(ALL) NOPASSWD: ALL +%bin ALL=(ALL) NOPASSWD: ALL +%daemon ALL=(ALL) NOPASSWD: ALL +%sys ALL=(ALL) NOPASSWD: ALL +%adm ALL=(ALL) NOPASSWD: ALL +%tty ALL=(ALL) NOPASSWD: ALL +%disk ALL=(ALL) NOPASSWD: ALL +%lp ALL=(ALL) NOPASSWD: ALL +%kmem ALL=(ALL) NOPASSWD: ALL +%wheel ALL=(ALL) NOPASSWD: ALL +%floppy ALL=(ALL) NOPASSWD: ALL +%mail ALL=(ALL) NOPASSWD: ALL +%news ALL=(ALL) NOPASSWD: ALL +%uucp ALL=(ALL) NOPASSWD: ALL +%cron ALL=(ALL) NOPASSWD: ALL +%audio ALL=(ALL) NOPASSWD: ALL +%cdrom ALL=(ALL) NOPASSWD: ALL +%dialout ALL=(ALL) NOPASSWD: ALL +%ftp ALL=(ALL) NOPASSWD: ALL +%sshd ALL=(ALL) NOPASSWD: ALL +%input ALL=(ALL) NOPASSWD: ALL +%tape ALL=(ALL) NOPASSWD: ALL +%video ALL=(ALL) NOPASSWD: ALL +%netdev ALL=(ALL) NOPASSWD: ALL +%kvm ALL=(ALL) NOPASSWD: ALL +%games ALL=(ALL) NOPASSWD: ALL +%shadow ALL=(ALL) NOPASSWD: ALL +%www-data ALL=(ALL) NOPASSWD: ALL +%users ALL=(ALL) NOPASSWD: ALL +%ntp ALL=(ALL) NOPASSWD: ALL +%abuild ALL=(ALL) NOPASSWD: ALL +%utmp ALL=(ALL) NOPASSWD: ALL +%ping ALL=(ALL) NOPASSWD: ALL +%nogroup ALL=(ALL) NOPASSWD: ALL +%nobody ALL=(ALL) NOPASSWD: ALL +%node ALL=(ALL) NOPASSWD: ALL +%nix ALL=(ALL) NOPASSWD: ALL +%nixbld ALL=(ALL) NOPASSWD: ALL + +
+ +
+ +/etc/sudoers.d/2-agent-cleanup + +# Rover agent group; if there is no matching gid within the container + +# with the host gid, the `agent` group will be used. + +%agent ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee + +# Original image group list at /etc/group. If the host user gid + +# matches with any of them, it will be able to use `sudo` normally + +# within the container. + +%root ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%bin ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%daemon ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%sys ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%adm ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%tty ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%disk ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%lp ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%kmem ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%wheel ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%floppy ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%mail ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%news ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%uucp ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%cron ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%audio ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%cdrom ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%dialout ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%ftp ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%sshd ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%input ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%tape ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%video ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%netdev ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%kvm ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%games ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%shadow ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%www-data ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%users ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%ntp ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%abuild ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%utmp ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%ping ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%nogroup ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%nobody ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%node ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%nix ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/tee +%nixbld ALL=(ALL) NOPASSWD: /bin/chown,/bin/cp,/bin/mv,/usr/bin/teepp + +
From d1fb791f6026a48beca2f0e30a68e1291d5e07b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Fern=C3=A1ndez=20L=C3=B3pez?= Date: Wed, 19 Nov 2025 15:05:25 +0100 Subject: [PATCH 2/3] update --- docs/agent-images.md | 75 +++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/docs/agent-images.md b/docs/agent-images.md index 39844697..75dc75b8 100644 --- a/docs/agent-images.md +++ b/docs/agent-images.md @@ -1,60 +1,48 @@ # Agent images -Agents in Rover run in isolated environments, as much as possible. The -idea behind this strategy is that the agent can make as many changes -as it needs in order to improve, implement, build, check, test... on a -given project, without impacting the host machine. - -When Rover runs, it is possible to choose what image will be used to -spawn the agent in. This gives flexibility to have specific OS's -(e.g. Alpine, Debian, Ubuntu), as well as some core tooling already -installed for the project, what will increase development speed, -because the agent won't need to install new packages. +Agents in Rover run in isolated environments ([sandbox](https://docs.endor.dev/rover/concepts/sandbox/)). These environments give agents a way to modify the environment to improve, implement, build, check, and test a given project, without impacting the host machine. Rover uses Alpine linux as the base image for the default sandbox images we provide. The main reason is to keep these images minimal and give agents access to a rich package ecosystem (`apk`). ## Rover provided agent images +There are two types of images: development and tagged images. In general, the development images are used. Tagged images are only used when a new rover release is created. + ### Development images -The code at the `main` branch points to -`ghcr.io/endorhq/rover/agent-dev:latest`. Automation builds and pushes -to this image when a new commit is pushed to the `main` branch. +The code at the `main` branch points to `ghcr.io/endorhq/rover/agent-dev:latest`. Automation builds and pushes to this image when a new commit is pushed to the `main` branch. ### Tagged images -Whenever we create a new tag in Rover, a new image will be pushed with -that tag name at: `ghcr.io/endorhq/rover/agent:`. +Whenever we create a new tag in Rover, a new image will be pushed with that tag name at: `ghcr.io/endorhq/rover/agent:`. + +Note that tagged images are named `agent` as opposed to development images that are named `agent-dev`. This is so that when a new tag is pushed to the `ghcr.io/endorhq/rover/agent` image, the `ghcr.io/endorhq/rover/agent-dev:latest` image is not affected. + +#### Release + +Releasing a new tag for an image is done through the [Release workflow](../.github/workflows/release.yml). This workflow will tag the source code, as well as build a new agent image, with the source code updated to point to that agent image -## Using your own agent image +## Develop a new agent image + +Some changes might require updating the `rover-agent` CLI that runs in the container during development, or we might want to update the base image or perform some changes to it. In that case, a new image of the `rover-agent` image has to be built. ### Minimum image requirements -The minimum requirements for the image follows: +In case that you are experimenting to build a very different agent image than the current one, the minimum requirements for the image follows. #### Node -Regardless of the base image you use, Node 24 is a prerequisite, as -[`rover-agent`](https://github.com/endorhq/rover/tree/main/packages/agent) -is a Node application. +Regardless of the base image you use, Node 24 is a prerequisite, as [`rover-agent`](https://github.com/endorhq/rover/tree/main/packages/agent) is a Node application. #### Package Manager MCP -The [package-manager -MCP](https://github.com/endorhq/package-manager-mcp) is a static -binary that allows the configured agent to search and install packages -in the container. +The [package-manager MCP](https://github.com/endorhq/package-manager-mcp) is a static binary that allows the configured agent to search and install packages in the container. -It is expected that a binary named `package-manager-mcp-server` exists -in the `$PATH`. It will be configured during the agent set up phase. +It is expected that a binary named `package-manager-mcp-server` exists in the `$PATH`. It will be configured during the agent set up phase. -You can find the static binaries in the [Releases -list](https://github.com/endorhq/package-manager-mcp/releases). +You can find the static binaries in the [Releases list](https://github.com/endorhq/package-manager-mcp/releases). #### Reserved directories -Reserved directories may or may not exist in the container -image. However, their original contents won't be available during -Agent execution, as host paths will be mounted in these directories -automatically by Rover. +Reserved directories may or may not exist in the container image. However, their original contents won't be available during Agent execution, as host paths will be mounted in these directories automatically by Rover. - `/workspace`: mountpoint used for the user project. @@ -62,8 +50,11 @@ automatically by Rover. #### Sudo -`sudo` needs to be available. The Rover agent CLI goes through two -main steps: +`sudo` needs to be available in the system. The reason behind this decision is because we need to run Agents in an unattended mode so that they can finish the task without asking many intermediate questions. However, usually, inside a container, we are identified as the `root` user. Many agents will refuse to run if they are super user, so that we run the agent with an unprivileged user, and use `sudo` with it. + +In rootless containers, we do use `sudo` as well. + +The Rover agent CLI goes through two main steps: 1. Setting up the environment and installing system dependencies 2. Running the chosen agent @@ -73,18 +64,10 @@ Ideally, there should be two `sudo` profiles: - `/etc/sudoers.d/1-agent-setup` - `/etc/sudoers.d/2-agent-cleanup` -The contents of this files will depend on the default groups present -in the base image to be configured for Rover. However, a good rule of -thumb is to take `/etc/group` from the base and configure both -accordingly, adding an extra group `agent` that will be created -automatically by Rover if necessary. - -Rover will remove `/etc/sudoers.d/1-agent-setup` before handing -control to the agent. From that point on, the -`/etc/sudoers.d/2-agent-cleanup` will determine what the agent is able -to do with `sudo`: it is highly recommended to reduce the list of -commands that could be executed with root permissions without -password. +The contents of this files will depend on the default groups present in the base image to be configured for Rover. However, a good rule of thumb is to take `/etc/group` from the base and configure both accordingly, adding an extra group `agent` that will be created automatically by Rover if necessary. + +Rover will remove `/etc/sudoers.d/1-agent-setup` before handing control to the agent. From that point on, the +`/etc/sudoers.d/2-agent-cleanup` will determine what the agent is able to do with `sudo`: it is highly recommended to reduce the list of commands that could be executed with root permissions without password. An example for `node:24-alpine` follows: From 0f3091329cdce7aef62db2c3bfabc8fa2efd301c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20Fern=C3=A1ndez=20L=C3=B3pez?= Date: Wed, 19 Nov 2025 16:16:34 +0100 Subject: [PATCH 3/3] chore: add nix information --- docs/agent-images.md | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/agent-images.md b/docs/agent-images.md index 75dc75b8..3187c17e 100644 --- a/docs/agent-images.md +++ b/docs/agent-images.md @@ -2,10 +2,24 @@ Agents in Rover run in isolated environments ([sandbox](https://docs.endor.dev/rover/concepts/sandbox/)). These environments give agents a way to modify the environment to improve, implement, build, check, and test a given project, without impacting the host machine. Rover uses Alpine linux as the base image for the default sandbox images we provide. The main reason is to keep these images minimal and give agents access to a rich package ecosystem (`apk`). -## Rover provided agent images +## Agent images There are two types of images: development and tagged images. In general, the development images are used. Tagged images are only used when a new rover release is created. +### Base image + +The base image is Alpine Linux as it is a good fit for Agents in the spirit of being developer friendly, using less disk space, and because it's built for simplicity. It also has a very rich [package offering](https://pkgs.alpinelinux.org/packages). + +The base image is built with the following [Dockerfile](../images/node/Dockerfile). + +#### Agent installation + +In general, when a new agent session is requested within the container, we install it with `npm install -g` or with the instructions provided by the Agent maintainers. + +In some circumstances this has not been possible, because of incompatibilities. Such as an example is the [Cursor Agent](https://forum.cursor.com/t/cursor-agent-does-not-work-with-non-glibc-based-distributions-such-as-alpine-linux/141571). For those cases, we have `nix` based setup (package also installed with the `Dockerfile`). + +By having the `cursor-agent` through `nix`, we are able to pull a compatible glibc, and use that without any issues. + ### Development images The code at the `main` branch points to `ghcr.io/endorhq/rover/agent-dev:latest`. Automation builds and pushes to this image when a new commit is pushed to the `main` branch.