What are GitLab groups and projects, and how does the organizational hierarchy work?
Quick Answer
GitLab organizes work into a hierarchy of groups, subgroups, and projects. Groups are containers for related projects and can nest into subgroups up to 20 levels deep. This structure enables shared CI/CD settings, centralized access control, and consistent pipeline templates across multiple projects within an organization.
Detailed Answer
Think of GitLab's organizational hierarchy like the structure of a large corporation. The company itself is the top-level group (acme-corp). Departments are subgroups (engineering, marketing, operations). Teams within departments are deeper subgroups (engineering/backend, engineering/frontend). Individual product repositories are projects within those team subgroups (engineering/backend/payment-api). Just as company-wide policies cascade down to every department and team, GitLab group settings flow down to every nested subgroup and project.
Groups in GitLab are namespaces that contain projects and optionally other subgroups. Every project belongs to exactly one group or personal namespace, and its URL reflects the hierarchy: gitlab.com/acme-corp/engineering/backend/payment-api. Groups provide centralized management for members and access levels: adding a user as a Developer on the engineering group automatically grants Developer access to every project within that group and its subgroups. This inheritance model eliminates the need to manage permissions project by project. Groups can also hold shared Runners, CI/CD variables, deploy tokens, and package registries that all child projects inherit. Labels and milestones defined at the group level appear in all child projects, enabling cross-project issue tracking and planning.
Internally, GitLab implements namespace inheritance through a parent-child relationship stored in the database. Each group has a parent_id (null for top-level groups) and a traversal_ids array that efficiently resolves the full ancestry chain. When evaluating permissions for a user accessing a project, GitLab walks up the namespace tree, checking memberships at each level. The most permissive role from any level in the chain becomes the effective permission. CI/CD variables defined at a group level are injected into pipelines of all child projects, with project-level variables taking precedence over group-level ones if they share the same key. This variable inheritance is a powerful mechanism for distributing shared secrets like Docker registry credentials or cloud provider API keys without configuring them in each project individually.
In production, organizations use groups to mirror their team structure and enforce governance. A typical enterprise setup has a top-level group per business unit (e.g., acme-corp/platform, acme-corp/product, acme-corp/data), with subgroups for teams (acme-corp/platform/sre, acme-corp/platform/security). Each team's subgroup contains their projects. The platform/sre group might contain infrastructure-modules, monitoring-stack, and incident-runbooks. Shared Runners registered at the top-level group are available to all projects, providing a consistent execution environment. Group-level CI/CD variables store shared secrets like DOCKER_REGISTRY_PASSWORD and SONAR_TOKEN, so individual projects do not need to duplicate these. Group-level merge request approval settings enforce minimum review requirements across all projects, and group-level push rules (in Premium tier) enforce commit message formatting or prevent certain file types from being committed.
A common gotcha is overcomplicating the group structure. While GitLab supports up to 20 levels of nesting, most organizations should use two to three levels at most (company > department > team). Deep nesting creates confusing URLs and makes it harder to find projects. Another pitfall is not understanding variable precedence: if the same variable is defined at the instance level, group level, subgroup level, and project level, the project-level value wins. This can lead to confusion when a developer overrides a group variable in their project without realizing it, causing their pipeline to behave differently from others in the same group.