Creating and Managing Groups, Roles, and Directories
TL;DR
- Groups organize users with similar permissions (e.g.,
admin,developer,analyst) - Roles define permission bundles for specific services (e.g.,
conda_store_admin,jupyterhub_developer) - Scopes are fine-grained permissions attached to roles (e.g.,
read:users:name) - Create roles → Assign to groups → Add users to groups → Users must log out/in for changes to take effect
- Group directories in JupyterHub require the
allow-group-directory-creation-role(since version 2024.9.1)
Overview
Groups are a fundamental and vital part of the Nebari ecosystem. They are used to manage access to a wide range of services within Nebari, including JupyterHub instances, Keycloak realms, Conda environments, and computing resources. By grouping users based on roles, projects, or departments, Nebari simplifies the management of permissions and resource sharing.
Beyond managing access, groups play a crucial role in organizing and sharing data across the JupyterHub ecosystem. Each group can have its own shared directory within JupyterHub, allowing users within the same group to collaborate seamlessly by sharing files and resources. This facilitates team collaboration and ensures that data is organized and accessible to those who need it.
In this document, we will cover:
- Understanding Roles, Groups, and Scopes
- Managing Groups in Keycloak
- Default Clients, Groups, and Permissions
- Creating Custom Roles with Scopes
- Managing Users and Group Membership
- Managing Group Directories in JupyterHub
- Permission Precedence and Troubleshooting
Understanding Roles, Groups, and Scopes
Nebari uses Keycloak to manage access for core services like jupyterhub and conda-store. Access is expressed through three related concepts:
- Roles: Named permission bundles for a specific client/service (e.g.,
conda_store_admin,jupyterhub_developer). Roles define what actions can be performed. - Groups: Collections of users who should receive the same roles (e.g.,
admin,developer,analyst). Groups aggregate users with similar responsibilities. - Scopes: Fine-grained permission strings attached to roles (e.g.,
read:users:namefor JupyterHub, oradmin!namespace=analystfor Conda-Store). Scopes are the actual permissions. - Components: Specifies the service that a role's scopes apply to (e.g.,
jupyterhuborconda-store).
Permission Flow: User → Group → Role → Scopes → Permissions
Nebari ships with default clients, groups, and roles that cover most needs. When you need more granular control, create custom roles with specific scopes in Keycloak and assign them to a group (or directly to a user).
Creating or editing a role does nothing until it is assigned via Role Mappings (to a group or user). Users must log out and back in for changes to take effect because Keycloak issues new access tokens on login.
Roles in Nebari can stack, meaning that if a user is in one group with role conda_store_admin and another group with role conda_store_viewer, this user ultimately has the role conda_store_admin. In case of different levels of access originating from different groups, precedence is given to the highest level of access. See Permission Precedence for details.
Default Clients, Groups, and Permissions
A fresh Nebari deployment comes with several custom Keycloak clients (services) enabled. You can manage these from Clients in the Keycloak admin console:
clients:
- jupyterhub
- conda_store
- grafana (if monitoring is enabled)
- argo-server-sso (if argo is enabled)
- forwardauth
To manage and configure these clients, you can navigate to the Clients tab within the Keycloak admin console, as illustrated in the image below.

This can be accessed at <nebari-url>/auth/admin/master/console/#/realms/nebari/clients
Default Groups and Their Roles
Groups represent a collection of users that perform similar actions and therefore require similar permissions. By default, Nebari is deployed with the following groups: admin, developer, and analyst (in roughly descending order of permissions and scope).
Roles on the other hand represent the type or category of user. This includes access and permissions that this category of user will need to perform their regular job duties. The differences between groups and roles are subtle. Particular roles (one or many), like conda_store_admin, are associated with a particular group, such as admin and any user in this group will then assume the role of conda_store_admin.
Membership in one of the default groups also grants access to the corresponding shared folder (e.g., the developer group has access to ~/shared/developer). Since 2024.9.1, Nebari requires a special role to access the shared folders: allow-group-directory-creation-role.
Default Roles and Permissions
Below is a table that outlines the default roles, groups, and permissions that come with Nebari:
- Analyst
- Developer
- Admin
- Super Admin
| Resources | Roles | Permissions |
|---|---|---|
|
|
|
| Resources | Roles | Permissions |
|---|---|---|
|
|
|
| Resources | Roles | Permissions |
|---|---|---|
|
|
|
| Resources | Roles | Permissions |
|---|---|---|
|
|
|
Managing Groups in Keycloak
Keycloak is the identity and access management service used in Nebari. It allows you to create and manage users, groups, roles, and permissions. Groups in Keycloak are collections of users, and they can be assigned specific roles that grant permissions to access various services within Nebari.
For detailed information on managing groups in Keycloak, refer to the Keycloak documentation on Group Management.
Below we outline the steps specific to Nebari.
Creating a New Group
To create a new group in Keycloak:
- Log in to Keycloak as an administrator (usually the
rootuser). - Navigate to Groups: Click on Groups in the left-hand menu.
- Create the Group: Click the New button, enter an appropriate name for your new group (e.g.,
conda-store-manager), and save.
If you wish to organize your groups hierarchically, you can create subgroups by selecting a parent group and adding a subgroup.
Example Workflow: Creating a conda-store-manager group
- Click New in the upper-right corner under Groups.

- Give the new group an appropriate name.

- Under Role Mapping, add the appropriate Client Roles as needed; there should be no need to update the Realm Roles.

In this example, the new group only has one mapped role, conda_store_admin; however, it's possible to attach multiple Client Roles to a single group.

Managing Subgroups
Subgroups allow you to create a hierarchical structure of groups, representing organizational units, projects, or teams.
To manage subgroups:
- Creating Subgroups: Select the parent group, navigate to the Sub Groups tab, and create a new subgroup.
- Assigning Roles to Subgroups: Assign roles to subgroups following the same process as regular groups.
- Use Cases: Organize groups hierarchically to reflect organizational structures (e.g.,
engineering→data-science,engineering→backend).
For more information, see the Keycloak documentation on Group Hierarchies.
Deleting a Group
To delete a group:
- Navigate to Groups: Click on Groups in the left-hand menu.
- Select the Group: Click on the group you want to delete.
- Delete: Click the Delete button and confirm.
Important: Deleting a group does not delete the users in that group. Users will simply lose the permissions associated with that group. Any shared directories associated with the group will remain in the file system but will no longer be mounted for users.
Creating Custom Roles with Scopes
Beyond Nebari's default roles, you can also create your own with highly specific permissions.
Understanding Components and Scopes
In Nebari, scopes are closely tied to the service (client) you're working with—in Keycloak roles we identify them as "components."
Components Attribute
The value of the component attribute depends on the service we are creating a role for. Currently, Nebari supports:
jupyterhub: to createjupyterhubnative roles in thejupyterhubclient.conda-store: to createconda-storeroles in theconda_storeclient.
Scope Syntax by Service
JupyterHub Scopes
JupyterHub scopes syntax in Nebari follow JupyterHub's built-in RBAC syntax.
Syntax: scope1,scope2,scope3
Example:
shares!user,read:users:name,read:groups:name
shares!user: Allows sharing servers with other users.read:users:name: Grants read access to other users' names.read:groups:name: Grants read access to other groups' names.
This combination allows a user to share their server with others (via shares!user) and also read other users' and groups' names.
Common Use Case - App Sharing:
shares!user=alice,shares!group=data-science,read:users:name,read:groups:name
Allows sharing apps with user alice and the data-science group.
For a complete list of JupyterHub scopes, see the JupyterHub documentation: https://jupyterhub.readthedocs.io/en/stable/rbac/scopes.html#available-scopes
Nebari extends these scopes for some integrations (e.g., jhub-apps) to support sharing apps with users/groups.
Conda-Store Scopes
Conda-Store scopes are defined at the namespace level.
Syntax: <access_level>!namespace=<namespace_name>
Access Levels: admin, developer, viewer
Example:
admin!namespace=analyst,developer!namespace=nebari-git
admin!namespace=analyst: Grants an admin role in theanalystnamespace.developer!namespace=nebari-git: Grants a developer role in thenebari-gitnamespace.
Common Use Case - Multi-namespace Access:
admin!namespace=data-science,viewer!namespace=production,viewer!namespace=shared-datasets
Grants admin access to the team's namespace, but read-only to production and shared datasets.
Nebari grants some Conda-Store permissions automatically, regardless of how your Keycloak groups are configured. The authentication class applies these defaults at login so users can work even if the Keycloak groups don't define any conda_store_* client roles.
What Nebari applies by default
- Personal namespace → always admin
"{username}/*" → {"admin"}(cannot be downgraded by scopes). - Deployment default namespace → viewer
"{default_namespace}/*" → {"viewer"}. - Nebari default groups (
analyst,developer,admin) → handled internally If a user is in one of these groups, Nebari binds a same-named namespace to the user with that group's effective Conda-Store role (e.g., groupanalyst⇒analyst/*), even when the Keycloak group itself has no client roles. - SuperAdmin (
conda_store_superadmin) → full wildcard"*/*" → {"admin"}(includes delete & service-token privileges).
How it reconciles with Keycloak scopes
- On each login, Nebari re-applies permissions from Keycloak role scopes (e.g.,
admin!namespace=analyst). - If multiple grants target the same namespace, the highest permission wins.
- Order of precedence (highest → lowest): SuperAdmin → explicit scopes → internal defaults.
- Changes take effect after the user signs in again.
Step-by-Step: Creating a Custom Role
Typical Workflow:
- Create a role (and define its scopes) under the relevant Client (e.g.,
jupyterhub,conda_store). - Assign this role to a group (or directly to an individual user).
- Add users to that group if you haven't already.
- Users log out and back in for changes to take effect.
Step 1: Create the Role
In the following example, we create a new role for the JupyterHub client granting permissions to share apps with users and groups.
- Select the appropriate client and click on Add Role.

- On the Add Role form, write a meaningful name and description for the role. Click Save.

- Now the role has been created, but it does nothing. Add permissions by clicking on the Attributes tab and adding
componentsandscopes.

Step 2: Assign Role to a Group
Creating a role in Keycloak has no effect on any user or group's permissions until it is assigned.
To add a role to a group:
- Select Groups on the left sidebar and search for the group.
- Select that group and click on the Role Mappings tab.
- Select the client associated with the role from the Client Roles dropdown.
- Select the role in Available Roles and click Add Selected >>.
Once complete, any users in this group will inherit the role's permissions after they log out and back in.
Step 3: Assign Role to a User (Alternative)
To add a role directly to a user:
- Select Users on the left sidebar and search for the user.

- Select that user and click on the Role Mappings tab.

- Select the client associated with the role.

- Select the role in Available Roles and click Add Selected >>.

Example: Custom Role Scenarios
Scenario 1: Data Science Team Lead
- Goal: Admin access to team namespace, read-only to production
- Component:
conda-store - Scopes:
admin!namespace=data-science,viewer!namespace=production
Scenario 2: App Collaborator
- Goal: Share JupyterHub apps with specific users and groups
- Component:
jupyterhub - Scopes:
shares!user,shares!group,read:users:name,read:groups:name
Scenario 3: Cross-functional Analyst
- Goal: Developer access to multiple namespaces
- Component:
conda-store - Scopes:
developer!namespace=marketing,developer!namespace=sales,viewer!namespace=finance
Managing Users and Group Membership
Adding Users to a Group
To add users to a group:
- Navigate to Users: Click on Users in the left-hand menu.
- Select a User: Choose the user you want to add to a group.
- Assign to Group:
- Click on the Groups tab within the user's details.
- Click the Join button.
- Select the group (and subgroup, if applicable) you wish to add the user to.
- Click Join to add the user to the group.
Repeat this process for all users who should be part of the group.
Removing Users from a Group
To remove users from a group:
- Navigate to Users: Click on Users in the left-hand menu.
- Select a User: Choose the user you want to remove from a group.
- Remove from Group:
- Click on the Groups tab within the user's details.
- Find the group in the Group Membership list.
- Click Leave next to the group name.
The user will immediately lose access to group-level permissions, but changes won't take effect until their next login (when a new access token is issued).
Removing Roles from Users or Groups
To remove a role from a user or group:
- Navigate to the user or group's Role Mappings tab.
- Under Assigned Roles, find the role you want to remove.
- Select the role and click Remove selected.
Again, changes take effect after the user logs out and back in.
Managing Group Directories in JupyterHub
As of version 2024.9.1, JupyterHub creates and mounts directories only for groups with the allow-group-directory-creation-role. By default, this includes the admin, analyst, and developer groups. Previously, directories were automatically created for all Keycloak groups. This change gives administrators more control over shared directories and overall access management without cluttering the file system.
Understanding Group Directories
A group directory in JupyterHub is a shared folder accessible to all members of a specific group. It provides a shared space within the file system for collaboration.
An example directory structure:
/shared
├── admin
│ ├── file1.txt
│ └── file2.txt
├── analyst
│ ├── file1.txt
│ └── file2.txt
└── developer
├── file1.txt
└── file2.txt
Assigning the allow-group-directory-creation-role to a Group
To enable directory creation and mounting for a group in JupyterHub, assign the allow-group-directory-creation-role to the group in Keycloak.
- Navigate to the Group: Log in to Keycloak as an administrator and select the group.
- Go to Role Mappings: Click on the Role Mappings tab.
- Assign the Role:
- Under Client Roles, select the
jupyterhubclient. - Select
allow-group-directory-creation-roleand click Add selected.
- Under Client Roles, select the
Users in this group will now have access to the group's shared directory in JupyterHub after they log out and back in.
Removing Group Directory Access
To remove the group's directory access:
- Navigate to the Group's Role Mappings: Select the group and go to the Role Mappings tab.
- Remove the Role:
- Under Assigned Roles, select
allow-group-directory-creation-role. - Click Remove selected.
- Under Assigned Roles, select
Data Preservation: No data is deleted when you remove the role; the directory is simply unmounted from users' JupyterLab sessions.
Managing Subgroup Directories
Subgroups can have their own directories in JupyterHub if assigned the allow-group-directory-creation-role. Assign the role to subgroups as needed to control access and collaboration.
Example Hierarchy:
/shared
├── engineering
│ ├── data-science
│ │ └── models
│ └── backend
│ └── configs
└── marketing
└── campaigns
Permission Precedence and Troubleshooting
How Permissions Stack
When a user belongs to multiple groups or has multiple roles assigned, Nebari uses the following rules to determine effective permissions:
-
Highest Permission Wins: If conflicting permissions exist, the most permissive one takes precedence.
- Example: User in both
viewergroup (read-only) andadmingroup (read-write) → user gets read-write access
- Example: User in both
-
Service-Specific Precedence:
- Conda-Store: SuperAdmin > Explicit Scopes > Internal Defaults > No Access
- JupyterHub: Scopes are additive; all granted scopes apply
-
Personal Namespaces: Always admin access in Conda-Store (cannot be downgraded)
Common Issues and Solutions
Issue 1: Changes Not Taking Effect
Symptom: User was added to a group or assigned a role, but still can't access resources.
Solution:
- User must log out completely and log back in
- Keycloak issues new access tokens on login with updated permissions
- Refreshing the page is not sufficient
Issue 2: User Has Too Many Permissions
Symptom: User can access resources they shouldn't be able to.
Solution:
- Check all groups the user belongs to
- Check direct role assignments to the user
- Remember: highest permission wins, so remove the more permissive role/group
- User must log out and back in
Issue 3: Group Directory Not Appearing
Symptom: Users in a group can't see the shared directory.
Solution:
- Verify the group has
allow-group-directory-creation-roleassigned - User must log out and back in to mount the directory
- Check JupyterHub logs for errors
- Verify the directory exists in the file system
Issue 4: Custom Role Not Working
Symptom: Created a custom role with scopes, but it has no effect.
Solution:
- Verify
componentsattribute is set correctly (e.g.,jupyterhuborconda-store) - Verify
scopesattribute syntax matches the service requirements - Ensure role is assigned to a group or user via Role Mappings
- User must log out and back in
Testing Permissions
To verify a user's permissions:
- JupyterHub: Check the user's token scopes via the JupyterHub API:
curl -H "Authorization: token <user-token>" \
https://<nebari-url>/hub/api/user
-
Conda-Store: Attempt to access namespaces through the UI or API and observe access levels
-
Keycloak: Review the user's Role Mappings and Group Membership tabs