GitLab Merge Requests for Infra Changes
Why MRs matter for infrastructure
For infrastructure code, the MR is not just a review nicety — it is a safety control. A bad Ansible change can take down a service or misconfigure dozens of machines. The MR gives:
- A mandatory second set of eyes before anything reaches production
- Automated checks (lint, syntax, dry-run) that run before review
- An auditable record of who approved what change and when
- A rollback point — you can revert an MR commit cleanly
Creating an MR
After pushing your branch:
- Go to the project in GitLab
- Click Merge Requests → New merge request
- Select your source branch and
mainas the target - Fill in the title and description
- Assign a reviewer
- Click Create merge request
GitLab often shows a banner on the project page when you push: "Create merge request for branch/name" — click that for a shortcut.
# After pushing, GitLab shows a direct link in the terminal output:
git push -u origin feature/add-ntp-server
# To create a merge request for feature/add-ntp-server, visit:
# https://gitlab.example.com/infra/repo/-/merge_requests/new?merge_request[source_branch]=feature/add-ntp-server
Writing a useful MR description
A good MR description for an infrastructure change answers:
- What — what is being changed
- Why — the ticket, reason, or request that prompted this
- Which hosts — what environments and hosts will be affected
- Risk — what could go wrong, what the rollback is
- Testing done — did you run --check --diff? On which hosts?
## What
Add internal NTP server to chrony configuration for all production hosts.
## Why
Ticket INF-2431 — external NTP access is being blocked by firewall policy.
## Affected hosts
All hosts in inventories/production/ (via group_vars/all.yml)
## Testing
Ran `ansible-playbook site.yml --check --diff -i inventories/production/hosts.ini`
Output shows only chrony.conf changing — diff attached to ticket.
## Rollback
Revert this MR commit. chrony will sync from external servers again on next run.
Reviewing an infra diff
When reviewing someone else's Ansible MR, look at:
Check which files changed
Use the Changes tab in the MR. The file list tells you the scope of the change before you read any code.
YAML / variable file changes
- Is the variable name correct and consistent with what the role expects?
- Is the value type right — string vs list vs dict?
- Is indentation correct? (2 spaces, no tabs)
- Is the change in the right file — all.yml vs groupname.yml vs host_vars?
Task file changes
- Does the task have a name?
- Is the correct module being used (not shell when a module exists)?
- Does it have
become: trueif needed? - Is
notify:present if the task should trigger a restart?
Template changes
- Does the Jinja2 syntax look correct?
- Are all
{% if %}blocks closed with{% endif %}? - Are variable names consistent with what is in defaults/ or group_vars/?
- Would this template produce correct output for different environments?
Checking pipeline status in an MR
The pipeline status is shown in the MR view — a green tick (passed), orange circle (running), or red X (failed).
Click the pipeline status to see individual job results. Click a failed job to read its log.
If the pipeline is failing on a lint rule you disagree with:
# Skip a specific rule on a task
- name: Run legacy script that we cannot replace yet
ansible.builtin.shell: /opt/legacy/setup.sh
# noqa: command-instead-of-shell
If the pipeline passes but you are not confident in the dry-run result, ask the author to paste the --diff output in the MR description.
Approval rules
Protected branches can require approvals before merge. Common setups:
- 1 approval required — minimum viable review; good for small teams
- CODEOWNERS approval — only specific people can approve changes to specific files or directories
- 2 approvals for production inventory changes — higher bar for high-risk files
If your MR is blocked on approvals, the MR page shows "Approvals: 0/1". Ask the designated reviewer to approve. You cannot approve your own MR.
Responding to review comments
When a reviewer leaves a comment requesting a change:
- Make the change in your local branch
- Add and commit:
git add . && git commit -m "Address review: fix variable name" - Push:
git push— the MR updates automatically - Reply to the comment in GitLab explaining what you changed, or resolve it if the reviewer left instructions you followed exactly
Squash commits
If you made many small commits during development ("fix typo", "another fix", "address review"), you can squash them into one clean commit at merge time.
Enable Squash commits in the merge dialog. GitLab combines all commits in your branch into one commit on main. The commit message is the MR title by default — edit it to something descriptive.
A squashed commit makes git log on main readable: one line per feature/fix, not a trail of work-in-progress commits.
Merging — what happens next
When all of these are satisfied:
- Pipeline passes
- Required approvals given
- No unresolved discussions
- Branch is up to date with main
Click Merge. GitLab merges your branch into main and (if configured) triggers a new pipeline on main — which may automatically deploy.
If a deploy pipeline runs after merge, watch it. If it fails, you need to act quickly — main now has code that does not deploy cleanly.
When an MR is stuck
- Pipeline failing — read the job log, fix the code, push again
- Merge conflicts — main has moved since you branched; rebase or merge main into your branch and resolve conflicts
- Waiting for approval — ping the reviewer or check if you need to add them
- Branch not up to date — click "Rebase" in the MR, or:
git fetch origin main && git rebase origin/main && git push --force-with-lease
# Bring your branch up to date with main
git fetch origin
git rebase origin/main
# If conflicts during rebase
git status # shows conflicting files
# edit files, resolve markers
git add resolved-file.yml
git rebase --continue
# Push the rebased branch (requires force)
git push --force-with-lease
--force-with-lease is safer than --force — it fails if someone else has pushed to your branch since you last pulled, preventing accidental overwrite.
Create MR from git push
You can open a GitLab MR directly from the git push command using push options (-o). GitLab prints the MR URL in the push output.
# Push branch and create an MR targeting main
git push -o merge_request.create origin feature/add-firewalld-rules
# Set a specific target branch
git push \
-o merge_request.create \
-o merge_request.target=main \
origin feature/add-firewalld-rules
# Set MR title at push time
git push \
-o merge_request.create \
-o merge_request.target=main \
-o merge_request.title="Add firewalld rules for web tier" \
origin feature/add-firewalld-rules
# Assign to a reviewer immediately
git push \
-o merge_request.create \
-o merge_request.assign=alice \
origin feature/add-firewalld-rules
GitLab outputs something like: remote: View merge request for feature/add-firewalld-rules: https://gitlab.internal/infra/config/-/merge_requests/42 — you can share this URL immediately without opening a browser.
Draft MRs
A Draft MR signals that the work is in progress and must not be merged yet. GitLab blocks the merge button until the Draft status is removed.
Create a Draft MR
# Method 1: Add "Draft:" prefix to the MR title in the UI
# Method 2: Create as Draft via push option
git push \
-o merge_request.create \
-o merge_request.title="Draft: Refactor nginx upstream config" \
origin feature/nginx-upstream-refactor
When to use Draft MRs
- Work is in progress — you want feedback but it's not ready to merge
- CI pipeline is failing and you are actively fixing it
- Waiting for a dependency (another MR must merge first)
- You want to open a discussion before finishing implementation
Removing Draft status
In the GitLab UI, click Mark as ready at the top of the MR page. This removes the Draft prefix and enables the merge button (subject to any approval rules).
CODEOWNERS file
A CODEOWNERS file in your repo root (or .gitlab/) automatically assigns reviewers based on which files are changed in an MR. This ensures the right team is always looped in for critical paths.
File location
# One of these locations (GitLab checks in order):
CODEOWNERS
docs/CODEOWNERS
.gitlab/CODEOWNERS
Syntax
# Pattern Owner(s)
# ──────────────────────────────────────────────────────────────
# All files (catch-all)
* @team-infra
# Specific directory — all files inside
/ansible/roles/ @team-infra
# Specific file type in any directory
*.tf @team-terraform
# Files in a specific directory by extension
/ansible/group_vars/*.yml @team-infra @team-security
# A specific file
/ansible/inventories/production/hosts.ini @team-infra-leads
# Group syntax (configure groups in GitLab Admin → Groups)
/deploy/ @infra/senior-ops
How CODEOWNERS interacts with approval rules
In GitLab, enable Settings → Merge requests → Code Owners → Require approval from code owners to make CODEOWNERS approval mandatory before merge. Without this setting, CODEOWNERS only adds reviewers automatically without blocking the merge.