What GitLab protected environments control and when regulated teams need them
Protected environments restrict who can deploy to a named environment, such as production, and who can run jobs that target it. A team that must prove only approved people deployed to production should record that restriction in GitLab itself, so the deployment permission is enforced by the platform and leaves a trail in pipeline history rather than depending on a checklist. The control attaches to the environment, which means the deploy job is what gets checked, not the person's general project role. Before you rely on it, confirm in the documentation for your installed version which roles, users and groups can be allowed to deploy, and whether approvals are required before a deployment starts. These details change between releases, so treat your own test instance as the reference.
Protected branches are a separate control. They govern who can push to a branch and who can merge into it, which decides what code reaches the point where a deployment is built. Protected environments govern the step after that: who may deploy that built code to a target. A regulated release needs both because either one alone leaves a gap. A protected production environment with an unprotected main branch lets an unreviewed change reach production through an approved deployer. A protected main branch with an open production environment lets any role with deploy access ship code that was reviewed. Map each environment to the branch or tag that feeds it, and write that mapping down as part of the change management design.
Runner placement is the third part of the picture. Protected runners only pick up jobs from protected branches or tags, so a deploy job can be kept off a runner that a feature branch could reach. Check the behavior against the GitLab Runner documentation for your Runner version, and confirm that runner tags, protected settings and environment rules line up. Ask your team which evidence an auditor will request: the approval record, the job log, the environment configuration export and the runner assignment. DeployOpen can prepare these controls and the evidence that shows them working, but the independent auditor or certification body makes the assessment.
How protected environments, protected branches and runners fit together
A deployment is only as controlled as the weakest of four points: the branch the code comes from, the environment the job targets, the runner that executes it, and the record that shows who did what. Each control covers a different part of that path, so the controls work together rather than substituting for one another.
Protected branches on the source
Protected branches decide who can push to and merge into a branch, and whether force pushes are allowed. They govern the code that enters a release, not the job that deploys it. A reviewer approval on a protected branch does not stop a maintainer from running a deployment from an unprotected branch unless the environment rules also apply. Confirm the merge and push settings available in your GitLab version, and whether approvals are enforced on the branch your release pipeline builds from.
Protected environments on deployment jobs
A protected environment lists the users, roles or groups allowed to run deployment jobs against a named environment such as production. It governs who can trigger a deploy to that target, and it can require approvals before a job runs. It does not decide what code is deployed or whether the job runs on a trusted runner. Check how approval rules interact with scheduled and manual jobs in your version, and confirm that production is the only environment with these rules applied.
Runners that execute deployment jobs
The runner runs the job's commands and holds the credentials and network access the deployment needs. Runner restrictions, such as limiting a runner to protected branches or to specific projects, decide where production jobs can execute. A protected environment does not control which machine runs the job. Keep production deployment runners separate from general build runners, keep runner and GitLab versions aligned as the Runner documentation advises, and confirm the tag and protection options in your Runner configuration.
Audit trail of who deployed
The audit trail is the record that shows which user triggered which deployment, against which environment and commit, and when. It supports change management evidence but does not prevent a deployment on its own. Decide how long audit events and job logs are kept outside GitLab, who can read them, and whether they are exported to your log platform. Confirm the audit event types and retention behavior available in your edition and version before relying on them as evidence.
Approvals and separation of duties
Approvals bring a second person into the release decision, which is the control most auditors ask about for production changes. Approvers should not be the same people who authored the change, and the rules must be tested on a non-production environment first. This control governs who signs off, not whether the signed-off code is tested. Confirm the approval counts and eligible approver options in your edition, and document who holds each role.
Which protected environment settings fit which release model
This table compares the main protected environment controls, including who may deploy, approvals, branch-linked deploy access and name-pattern scoping. Read each row to see when a single-team release fits and when a segregated production release needs the control, then confirm the option exists in your installed version.
| Setting | Single-team release | Segregated production release | Confirm in your installed version |
|---|---|---|---|
| Deploy access by role | Maintainer or developer roles allowed to deploy, with little separation between building and releasing | Limit production deploys to a small named group, separate from the people who merge code | Which roles can be granted deploy access on protected environments and whether Developer is included |
| Deploy access by user or group | Usually unnecessary, since role-based access covers a small team | Named users or groups tied to the release owner and on-call roster, reviewed on a set cadence | Whether individual users and groups can be added to deploy allowlists in your tier |
| Required approvals | Often off, since the team reviews changes before merge | Approvals from people outside the deploying group before a job runs against production | Whether required approvals on protected environments are available in your edition and tier |
| Deploy access tied to protected branches | Deploys from the default branch are enough, with branch protection kept on | Production deploys only from protected release branches or tags that require reviewed merges | Which branches are protected, whether tags can be protected, and how the pipeline reference is checked |
| Environment name patterns | One environment, such as production, with an exact name | Separate entries for each production cluster, region or customer tenant, each with its own rules | Whether wildcard names are supported in your version and how they match existing environment names |
| Runner scoping for deploy jobs | Shared runners are acceptable for low-risk deploy jobs | Runners restricted to protected branches or dedicated to production, with ephemeral executors | Runner tags and protected-runner settings available in your version and which runners can reach production |
How the setup differs for small teams and regulated production
A single application team with one production environment usually needs the smallest working setup: one production environment, one protected branch that feeds it, and a short list of maintainers allowed to deploy. The aim is to stop accidental or unreviewed deploys without building an approval process the team will route around. Environment names can stay simple, such as production and staging, and runners can often be shared with other non-sensitive jobs as long as the deploy job runs on a runner the team controls.
- Environment naming: Use one clearly named production environment and keep the name stable, since protection rules and deployment history attach to it.
- Approver group: Restrict deployers to a small set of maintainers, and confirm whether a second approval is practical for a team of this size.
- Runner placement: Run deploy jobs on a dedicated runner that holds production credentials, separate from general build runners.
- Evidence collection: Rely on the GitLab deployment history and merge request records, and export them on a schedule your team agrees.
- Review load: Keep the rule set short enough that the team does not bypass it during incidents.
Before going live, confirm which role your GitLab tier allows to change protected environment settings and who holds that role.
In a multi-team organization, the risk shifts from a single mistake to conflicting duties: the people who write code should not be the only people who approve its release to production, and no single team should be able to change the controls that govern its own deploys. Protected environments let you name the approver groups for each environment, so development, platform and security teams can each hold a defined part of the release path. The design work is mostly about which groups exist and who belongs to them.
- Environment naming: Create separate environments per product or region, such as
prod-payments-eu, so approvals and history stay scoped to the owning team. - Approver groups: Assign deploy rights to a release group and approval rights to a separate group, and avoid overlap between them for the same environment.
- Runner placement: Give each production environment its own runner pool so one team's jobs cannot run on another team's deployment credentials.
- Evidence collection: Map each approval to a named group, so a review can show which team approved a change without relying on individual memory.
- Administration: Limit who can change environment protection settings to a small platform group, and log those changes.
Confirm with your identity team how group membership is synchronized from your directory, since stale membership is a common source of unintended access.
A regulated release needs the deployment control to be provable after the fact. The control must show who approved a change, what was deployed, where it ran, and that the path could not be bypassed. Protected environments, protected branches and dedicated runners together form that path, and the evidence has to come out of the system of record in a form an independent assessor can review. Your compliance function and external auditors decide what evidence is sufficient; DeployOpen can prepare the configuration and collect the records, but it does not issue assessments or attestations.
- Environment naming: Use names that match the change-management system's identifiers, so each deployment can be traced to an approved ticket.
- Approver groups: Separate the people who approve a change from the people who execute it, and record the approval inside GitLab rather than in a side channel.
- Runner placement: Restrict production jobs to runners in the controlled network segment, and keep their configuration under version control.
- Evidence collection: Retain deployment records, approval history, audit events and runner configuration for the period your policy requires, and test that you can retrieve them.
- Break-glass process: Define in advance how an emergency change is approved, recorded and reviewed afterward.
Ask your auditors which evidence formats they accept and how long they expect records to be retained before you finalize the design. Confirm the GitLab tier you need for the relevant approval and compliance features with the current pricing page.
Setting up protected environments step by step
This sequence turns protected environments from a design idea into a tested control, so that only the people and jobs you approved can reach production and every change is traceable afterward.
Define environments and naming
Start by listing every deployment target your team uses, such as staging, pre-production and production, and give each one a stable name that matches how your pipelines refer to it. The platform or release lead should agree the list with the security owner before anyone edits project settings, because later renames break approval rules and deployment history. Confirm which projects will carry the production environment and whether shared environment names will span several repositories. The step is done when you have a written environment inventory with names, owners and target systems, and each production name appears exactly as it will be written in the CI/CD configuration.
Restrict the production branch with protected branches
Protect the branch that feeds production so that only maintainers can merge or push to it, and block direct pushes and force pushes. In the project settings for protected branches, set who may merge and who may push, and require the review and pipeline success checks your change policy already demands. Verify the setting by attempting a direct push to the branch from a developer account, which should be rejected. The step is complete when the rule is listed in the protected branches page and the rejected push appears as a failed attempt in your test notes.
Create approver and deployer groups
Create a small group of users who may deploy to production and, separately, a group of approvers who must sign off before a deployment runs. Keep the deployer list short and tie membership to your identity provider or group sync where possible, so removing someone from corporate access also removes deploy rights. In the production environment settings, add the deployer roles or groups and the required approvers, and check that they match the separation of duties in your release procedure. The step is done when the environment page shows the allowed deployers and approval rule, and a user outside both groups cannot see the deploy action.
Assign a dedicated runner or runner tag
Route production jobs to runners that only production pipelines can use, either by registering a runner restricted to protected branches and protected environments or by assigning a runner tag that only production jobs request. Runners that execute production deployments should not also run untrusted merge request jobs, so review whether the executor and host are isolated enough for that purpose. Confirm the runner's major and minor version matches your GitLab version, as GitLab advises. The step is complete when a production job runs on the dedicated runner, and a job from a feature branch that requests the same tag stays pending because it cannot use that runner.
Test with a non-production deployment
Run a complete deployment to a non-production environment that uses the same rules, including the approval step and the dedicated runner, so you can confirm the workflow before touching production. Walk through the approval request, the approval, the job execution and the environment's deployment record, and note who could see and trigger each step. Fix any gap, such as a missing approver or a job that ran on the wrong runner, and repeat the test until the sequence behaves as written. The step is done when the non-production run produces the expected deployment record and no job ran outside the approved path.
Test a rejected deployment attempt
Attempt a production deployment that should fail, such as a job from an unprotected branch, a run triggered by a user outside the deployer group, or a deployment without the required approval. The control is only proven when these attempts are blocked, so record each one, the reason GitLab gave and who saw it. Ask a second person in security or compliance to review the failed attempts, because they are the evidence that the control works. The step is complete when every negative test is blocked, and the outcomes are saved with the test date and tester names.
Record the configuration as evidence
Write down the final configuration: environment names, protected branch rules, deployer and approver groups, runner assignments, and the results of the positive and negative tests. Store this record in the system your change management process uses, and set a review point, such as when membership changes or the GitLab version is upgraded, so the settings are checked again. Confirm with your auditors or compliance lead which form of evidence they accept, since DeployOpen prepares the evidence but independent assessors decide whether it satisfies a requirement. The step is done when the record is stored, linked from the production environment's runbook, and the owner has signed it off.
Security risks that weaken deployment control
Protected environments only hold if the rules around them hold. The risks below sit at the edges of the production release path: who has rights to push, which branches and runners can reach production, which variables jobs can read, and who can change the environment rules themselves. Each card names the control that closes the gap and the related hardening topic to work through next.
Broad maintainer roles bypass intent
Maintainers can often push to branches and trigger pipelines that the team assumed only release managers could reach. If the production environment lists the deploy role broadly, a protected environment becomes a formality. Check who holds Maintainer or Developer on each production project, and confirm that the deployers allowed in the environment are a named, short list. Review group inheritance too, since memberships granted at a parent group flow down to every project under it. Related: GitLab sign-up and visibility restrictions and GitLab Admin Mode and administrator access.
Unprotected production branches
A protected environment only controls deployments that run from branches or tags that GitLab has protected. If the branch that feeds production accepts direct pushes or force pushes, a deployer can ship code that never passed review. Confirm that the release branch is protected, that direct pushes are blocked, and that merges require approval. Pair the branch rules with GitLab push rules and deploy keys so unverified commits and secret files are rejected before they reach the branch.
Shared runners running untrusted jobs
A deployment job that runs on a shared runner may execute alongside jobs from other projects, including merge request pipelines from forks or unreviewed branches. That runner can then hold credentials or network paths that reach production. Confirm that production deployments use runners dedicated to protected branches and tags, and that merge request jobs cannot pick them up. The hardening topic Isolating GitLab runners and privileged jobs covers ephemeral VMs and mixed-trust fleets.
CI variables exposed to non-production jobs
Deployment credentials stored as CI/CD variables are visible to any job that matches their scope. A variable marked for all environments can be read by a test job on a feature branch, which then sends it to an external service or prints it to a log. Mark production credentials as protected so they only reach protected branches, scope them to the production environment, and mark them masked and hidden where the value allows. For the most sensitive secrets, read Protecting secrets in GitLab CI/CD variables.
Administrators who can change environment rules
Protected environment settings are only as strong as the accounts that can edit them. An administrator or project owner who can remove an approval rule or add a deployer can weaken the control without anyone else noticing. Ask who can change environment settings, whether those changes are logged, and whether administrator sessions require re-authentication. Keep the number of full administrators small and review changes to protected environment rules on a fixed schedule. The topic GitLab Admin Mode and administrator access sets out the controls.
Pipeline changes that skip the gate
A deployment job can be altered in the same merge request that introduces the change, so a reviewer may approve a pipeline definition that quietly deploys to production without the required approval. Pinned images, vendored includes and locked tool versions reduce the chance that an outside change slips into the job. Check that changes to the CI configuration file require review from the owners of the release process, and that remote includes come from locations the team controls. See GitLab pipeline integrity: image digests and pinned dependencies for the controls to apply.
Questions teams ask before configuring protected environments
How much effort does a protected environment setup take in-house?
The configuration itself is small: a protected environment is a project setting that names the environments to protect and the roles, users or groups allowed to deploy to them. The effort sits around that setting. Your team must agree on who approves production releases, map those people to GitLab accounts, tie the approval rules to protected branches and runners, and test that a deploy from an unapproved branch or job is blocked. Expect most of the time to go into the policy decisions and the test evidence rather than the clicks in the interface. Ask any partner for a written scope that lists each of these steps before quoting.
What drives the effort up for protected environments?
Effort rises with the number of environments, the number of teams deploying to them, and how many deployment paths already exist. Each of these adds rules to agree, roles to map and pipelines to change:
- Count of production-like environments, such as staging, production and regional copies
- Number of projects that deploy, and whether they share pipeline templates
- Mixed trust in runners, which may require separate runners for protected branches
- Existing deployment scripts that push to servers outside GitLab, which must be moved or fenced off
- Audit expectations for who approved each release and where that record is kept
A single application with one production environment is a much smaller job than a shared template used by dozens of services.
What does the internal team need to provide?
The internal team must decide the release policy before any configuration begins. That means naming the environments that need protection, the people or groups who may approve deployments, and the branches that are allowed to reach production. Someone with GitLab administrator access must be available to change project settings and runner assignments, and someone from the security or change management side should review the policy and the test results. Your team also owns the identity mapping, so the users and groups in your identity provider must match the people named in the policy. Confirm these owners early, because most delays come from unresolved approval questions rather than from GitLab itself.
Can a team set up protected environments without outside help?
Yes, a team with GitLab administration experience can configure protected environments, protected branches and dedicated runners using the product documentation. The harder part is verification and evidence. Teams often skip tests that try to deploy from an unprotected branch, that check a job cannot use a runner outside its trust group, or that confirm the approval record is kept. An outside partner can bring a written test plan, configuration review and documentation for auditors, but the decision to outsource depends on whether your staff have the time and the independence to test their own work. DeployOpen implements, hardens and documents self-hosted GitLab inside your own infrastructure, and works from a written scope agreed before work starts.
How do protected environments affect release speed?
Protected environments add approval steps, so a release to production waits for a named person or group to act. That delay is intended, but it can slow urgent fixes if approvers are not reachable. Most teams reduce the impact by defining who may approve and at what hours, keeping staging deployments unrestricted, and rehearsing the emergency path before it is needed. Protected branches have a similar effect, since only maintainers or designated roles can merge into them. The speed cost is usually predictable once the approval policy is written, so ask your team to model a normal release and an urgent hotfix before rollout.
Which GitLab tiers include protected environments, and what should I confirm?
Confirm the feature list for each tier on the GitLab pricing page before you plan the build. The pricing page lists advanced CI/CD and priority support for Premium, and application security testing, software supply chain security, vulnerability management and compliance features for Ultimate. It does not, in the material used for this page, settle whether every protected environment control sits in a given tier, so check the feature availability notes in the product documentation for the exact version you run. GitLab Free costs 0 US dollars, Premium costs 29 US dollars per user per month billed annually, and Ultimate is custom priced, all for self-managed installations. Ask your GitLab sales contact to confirm in writing which tier includes the approval controls you need.
What evidence should we keep to show a release was approved?
Keep the approval policy, the list of approvers, the protected environment settings, and the deployment records that show who triggered and approved each production release. These records must be exported or retained according to your change management rules, since they are the proof an auditor will ask for. Confirm with your compliance team which records they accept and how long they must be kept. DeployOpen prepares systems, controls and evidence for compliance assessments, but independent auditors and certification bodies decide whether that evidence is sufficient.
Should protected environments and runner isolation be designed together?
Yes. A protected environment controls who may deploy, but it does not stop a malicious job from running on a shared runner. If production deploy jobs run on runners that also execute untrusted merge request pipelines, a compromised job could reach deployment credentials. Plan the runner pool alongside the environment rules: production jobs should run only on protected branches and on runners dedicated to trusted work. Your team should test that a merge request job cannot use those runners, because the environment setting alone does not enforce that boundary.
Limits to confirm in your own GitLab environment
Protected environments depend on several parts of your GitLab setup working together: the edition, the release you run, your identity provider, your runner fleet and your topology. Each card below is a question to answer with the vendor documentation or your own test instance before you write the release policy.
Edition and version availability
Confirm which deployment protection features your edition includes and whether they exist in the exact release you plan to run. The pricing page lists tier differences such as advanced CI/CD for Premium and compliance features for Ultimate, but feature placement can change between releases. Check the feature documentation for your version, and note that GitLab maintains only the current minor line plus the two prior monthly releases for security fixes, so an older release may lack a fix or setting you rely on.
Approval behavior with your identity provider
Ask how approver eligibility is resolved when users come from SAML, LDAP, SCIM or another provider. Group membership changes made in the identity provider may not reach GitLab immediately, so a user removed from a release group could still hold approval rights until sync runs. Test a deprovisioned user, a user moved between groups and a user with two identities, and record which result you observe.
Runner executor and tag limits
Confirm which executor types can serve a protected environment job and how runner tags and protected-branch restrictions interact. Shell, Docker and autoscaling executors differ in how isolation, credentials and ephemeral workers are handled, and a shared runner may pick up a deployment job you intended for a dedicated pool. Run a deployment from an unprotected branch against your production runner and confirm that it is refused.
Geo and multi-node behavior
Check how protected environment settings, approval rules and deployment records replicate to a Geo secondary site and across nodes in a multi-node layout. A failover to a secondary may expose a configuration state that differs from the primary if replication lags. Confirm which records are replicated, how a promoted site handles in-flight approvals and whether your runners reach the new site.
Audit event retention
Establish how long audit events for protected environment changes and approvals are kept, where they are stored and how they are exported. Your evidence for change management depends on these records surviving your own retention period. Ask the vendor documentation which events are recorded, then export a test change and confirm that the entry shows who changed the rule and when.
Backup coverage of deployment settings
Confirm that protected environment rules, approval configuration and deployment history are captured by your backup and restore process. A restore into a new instance should reproduce the same rules, so test it. Secrets stored in CI/CD variables and the instance secrets file may be needed to make restored deployment jobs work, so verify that both are backed up under your own procedure.
How to test that production deployments are actually restricted
Test the restriction before a regulated release depends on it. A protected environment is only useful if you have seen it refuse the wrong person, the wrong branch and the wrong runner. Build a short test plan with three attempts and record the result of each one. First, attempt a deployment to the production environment as a user who is not in the allowed deployers list, such as a developer with Maintainer access who was not added to the deploy approval rule. Second, from a feature branch that is not protected, create a pipeline that targets the production environment and confirm the job cannot reach it. Third, run the same job on a runner that is not tagged or scoped for production, and confirm GitLab does not dispatch it there. Each attempt should use an account and project settings that match your real production configuration, not a sandbox copy.
Record each attempt in a way a change reviewer can read later. For every test, keep the date, the GitLab version, the project and environment name, the user or service account used, the branch, the runner description and tags, the pipeline URL and the job log excerpt showing the block or approval prompt. Export the protected environment and protected branch settings at the same time, because a later edit can make an old result misleading. Store these records in the system where your change records already live, and link them from the change ticket. Ask your team to confirm the retention period their own policy requires, since this is a decision for your compliance owner, not for the test plan.
Repeat the test after every GitLab upgrade, every change to protected branch or runner settings and every change in group membership. Route upgrades through the documented upgrade stops first, then rerun the three attempts on a non-production copy and again on the live instance during the next scheduled window. The test shows that your configured controls work as intended in your environment. It does not establish that an independent assessor has reviewed or accepted those controls, so describe the output as internal test evidence and let your auditor decide what weight it carries.
How DeployOpen scopes and delivers protected environment controls
DeployOpen plans, implements, documents and supports self-hosted GitLab inside your own infrastructure, including the protected environment, protected branch and runner settings that control who can release to production. Each engagement starts from a written scope so your approvers, runner boundaries and evidence needs are agreed before any configuration changes are made.
How we scope
- Map which environments, such as staging and production, need deployment restrictions and who approves each release.
- Agree which branches may deploy to protected environments and which runners are permitted to execute those jobs.
- Record scope, deliverables and timelines in writing before work begins, since most engagements are fixed-scope.
How we build
- Configure protected environments with deployers, approval rules and required approvals on your self-managed instance.
- Pair protected branches with protected environments so only reviewed, merged code reaches production jobs.
- Test that unauthorized users and unprotected branches are blocked, and that approved deployments still succeed.
How we hand over
- Document the environment, branch and runner configuration, with the owner and reason for each access rule.
- Provide a test record showing blocked and permitted deployment attempts for your change management reviewers.
- Walk your platform and security teams through routine changes, such as adding a deployer or rotating a runner.
How we support
- Offer ongoing support, upgrades and continuous testing for protected environment settings as a retainer.
- Review access rules after GitLab upgrades so approval behavior stays as your written scope describes.
- Confirm with your auditors which evidence they expect, since DeployOpen prepares controls and records but does not issue audit reports.

